├── .clang-format ├── .drone.jsonnet ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── AddStaticBundleLib.cmake ├── GenVersion.cmake ├── MacroEnsureOutOfSourceBuild.cmake ├── StaticBuild.cmake ├── combine_archives.cmake ├── mingw-i686-toolchain.cmake └── mingw-x86-64-toolchain.cmake ├── docs ├── api │ ├── .gitignore │ ├── Makefile │ ├── api-to-markdown.py │ ├── make-docs.sh │ └── static │ │ ├── index.md │ │ └── sidebar.md └── config-merge-logic.md ├── external └── CMakeLists.txt ├── include └── session │ ├── blinding.h │ ├── blinding.hpp │ ├── bt_merge.hpp │ ├── config.h │ ├── config.hpp │ ├── config │ ├── base.h │ ├── base.hpp │ ├── community.h │ ├── community.hpp │ ├── contacts.h │ ├── contacts.hpp │ ├── convo_info_volatile.h │ ├── convo_info_volatile.hpp │ ├── encrypt.h │ ├── encrypt.hpp │ ├── error.h │ ├── expiring.h │ ├── expiring.hpp │ ├── groups │ │ ├── info.h │ │ ├── info.hpp │ │ ├── keys.h │ │ ├── keys.hpp │ │ ├── members.h │ │ └── members.hpp │ ├── namespaces.hpp │ ├── notify.h │ ├── notify.hpp │ ├── profile_pic.h │ ├── profile_pic.hpp │ ├── protos.hpp │ ├── user_groups.h │ ├── user_groups.hpp │ ├── user_profile.h │ ├── user_profile.hpp │ └── util.h │ ├── curve25519.h │ ├── curve25519.hpp │ ├── ed25519.h │ ├── ed25519.hpp │ ├── export.h │ ├── fields.hpp │ ├── hash.h │ ├── hash.hpp │ ├── multi_encrypt.h │ ├── multi_encrypt.hpp │ ├── onionreq │ ├── builder.h │ ├── builder.hpp │ ├── hop_encryption.hpp │ ├── key_types.hpp │ ├── parser.hpp │ ├── response_parser.h │ └── response_parser.hpp │ ├── platform.h │ ├── platform.hpp │ ├── random.h │ ├── random.hpp │ ├── session_encrypt.h │ ├── session_encrypt.hpp │ ├── sodium_array.hpp │ ├── types.hpp │ ├── util.hpp │ ├── version.h │ ├── xed25519.h │ └── xed25519.hpp ├── libsession-util.pc.in ├── proto ├── CMakeLists.txt ├── SessionProtos.pb.cc ├── SessionProtos.pb.h ├── SessionProtos.proto ├── WebSocketResources.pb.cc ├── WebSocketResources.pb.h └── WebSocketResources.proto ├── src ├── CMakeLists.txt ├── blinding.cpp ├── bt_merge.cpp ├── config.cpp ├── config │ ├── base.cpp │ ├── community.cpp │ ├── contacts.cpp │ ├── convo_info_volatile.cpp │ ├── encrypt.cpp │ ├── error.c │ ├── groups │ │ ├── info.cpp │ │ ├── keys.cpp │ │ └── members.cpp │ ├── internal.cpp │ ├── internal.hpp │ ├── protos.cpp │ ├── user_groups.cpp │ └── user_profile.cpp ├── curve25519.cpp ├── ed25519.cpp ├── fields.cpp ├── hash.cpp ├── multi_encrypt.cpp ├── onionreq │ ├── builder.cpp │ ├── hop_encryption.cpp │ ├── key_types.cpp │ ├── parser.cpp │ └── response_parser.cpp ├── random.cpp ├── session_encrypt.cpp ├── sodium_array.cpp ├── util.cpp ├── version.c.in └── xed25519.cpp ├── tests ├── CMakeLists.txt ├── catch2_bt_format.hpp ├── static_bundle.cpp ├── swarm-auth-test.cpp ├── test_blinding.cpp ├── test_bt_merge.cpp ├── test_bugs.cpp ├── test_compression.cpp ├── test_config_contacts.cpp ├── test_config_convo_info_volatile.cpp ├── test_config_user_groups.cpp ├── test_config_userprofile.cpp ├── test_configdata.cpp ├── test_curve25519.cpp ├── test_ed25519.cpp ├── test_encrypt.cpp ├── test_group_info.cpp ├── test_group_keys.cpp ├── test_group_members.cpp ├── test_hash.cpp ├── test_multi_encrypt.cpp ├── test_onionreq.cpp ├── test_proto.cpp ├── test_random.cpp ├── test_session_encrypt.cpp ├── test_xed25519.cpp └── utils.hpp └── utils ├── android.sh ├── ci ├── drone-docs-upload.sh ├── drone-format-verify.sh └── drone-static-upload.sh ├── deb.oxen.io.gpg ├── format.sh ├── ios.sh ├── macos.sh └── static-bundle.sh /.clang-format: -------------------------------------------------------------------------------- 1 | 2 | BasedOnStyle: Google 3 | AlignAfterOpenBracket: AlwaysBreak 4 | AlignConsecutiveAssignments: 'false' 5 | AlignConsecutiveDeclarations: 'false' 6 | AlignEscapedNewlines: Left 7 | AlignOperands: AlignAfterOperator 8 | AlignTrailingComments: 'true' 9 | AllowAllArgumentsOnNextLine: 'true' 10 | AllowShortBlocksOnASingleLine: 'false' 11 | AllowShortCaseLabelsOnASingleLine: 'true' 12 | AllowShortFunctionsOnASingleLine: Inline 13 | AllowShortIfStatementsOnASingleLine: 'false' 14 | AllowShortLoopsOnASingleLine: 'false' 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakTemplateDeclarations: Yes 17 | BreakBeforeBinaryOperators: None 18 | BreakBeforeBraces: Attach 19 | BreakBeforeTernaryOperators: 'true' 20 | BreakConstructorInitializers: AfterColon 21 | Cpp11BracedListStyle: 'true' 22 | KeepEmptyLinesAtTheStartOfBlocks: 'true' 23 | NamespaceIndentation: Inner 24 | CompactNamespaces: 'true' 25 | PenaltyBreakString: '3' 26 | SpaceBeforeParens: ControlStatements 27 | SpacesInAngles: 'false' 28 | SpacesInContainerLiterals: 'false' 29 | SpacesInParentheses: 'false' 30 | SpacesInSquareBrackets: 'false' 31 | Standard: c++17 32 | UseTab: Never 33 | SortIncludes: true 34 | ColumnLimit: 100 35 | IndentWidth: 4 36 | AccessModifierOffset: -2 37 | ConstructorInitializerIndentWidth: 8 38 | ContinuationIndentWidth: 8 39 | 40 | 41 | # treat pointers and reference declarations as if part of the type 42 | DerivePointerAlignment: false 43 | PointerAlignment: Left 44 | 45 | # when wrapping function calls/declarations, force each parameter to have its own line 46 | BinPackParameters: 'false' 47 | BinPackArguments: 'false' 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build*/ 2 | /compile_commands.json 3 | /.cache/ 4 | /.vscode/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/oxen-encoding"] 2 | path = external/oxen-encoding 3 | url = https://github.com/oxen-io/oxen-encoding.git 4 | [submodule "external/libsodium-internal"] 5 | path = external/libsodium-internal 6 | url = https://github.com/jagerman/libsodium-internal.git 7 | [submodule "tests/Catch2"] 8 | path = tests/Catch2 9 | url = https://github.com/catchorg/Catch2 10 | [submodule "external/ios-cmake"] 11 | path = external/ios-cmake 12 | url = https://github.com/leetal/ios-cmake 13 | [submodule "external/zstd"] 14 | path = external/zstd 15 | url = https://github.com/facebook/zstd.git 16 | [submodule "external/nlohmann-json"] 17 | path = external/nlohmann-json 18 | url = https://github.com/nlohmann/json.git 19 | [submodule "external/protobuf"] 20 | path = external/protobuf 21 | url = https://github.com/protocolbuffers/protobuf.git 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14...3.23) 2 | 3 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 4 | 5 | # Has to be set before `project()`, and ignored on non-macos: 6 | set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13 CACHE STRING "macOS deployment target (Apple clang only)") 7 | 8 | set(LANGS C CXX) 9 | find_program(CCACHE_PROGRAM ccache) 10 | if(CCACHE_PROGRAM) 11 | foreach(lang ${LANGS}) 12 | if(NOT DEFINED CMAKE_${lang}_COMPILER_LAUNCHER AND NOT CMAKE_${lang}_COMPILER MATCHES ".*/ccache") 13 | message(STATUS "Enabling ccache for ${lang}") 14 | set(CMAKE_${lang}_COMPILER_LAUNCHER ${CCACHE_PROGRAM} CACHE STRING "") 15 | endif() 16 | endforeach() 17 | endif() 18 | 19 | 20 | project(libsession-util 21 | VERSION 1.2.0 22 | DESCRIPTION "Session client utility library" 23 | LANGUAGES ${LANGS}) 24 | 25 | message(STATUS "${PROJECT_NAME} v${PROJECT_VERSION}") 26 | 27 | set(LIBSESSION_LIBVERSION ${PROJECT_VERSION}) 28 | 29 | include(GNUInstallDirs) 30 | 31 | list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") 32 | 33 | # No in-source building 34 | include(MacroEnsureOutOfSourceBuild) 35 | macro_ensure_out_of_source_build("${PROJECT_NAME} requires an out-of-source build. Create a build directory and run 'cmake ${PROJECT_SOURCE_DIR} [options]'.") 36 | 37 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 38 | set(libsession_IS_TOPLEVEL_PROJECT TRUE) 39 | else() 40 | set(libsession_IS_TOPLEVEL_PROJECT FALSE) 41 | endif() 42 | 43 | 44 | set(CMAKE_CXX_STANDARD 17) 45 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 46 | set(CMAKE_CXX_EXTENSIONS OFF) 47 | 48 | set(default_static_libstd OFF) 49 | if(WIN32) 50 | set(default_static_libstd ON) 51 | endif() 52 | 53 | option(BUILD_SHARED_LIBS "Build as shared library" OFF) 54 | 55 | if(BUILD_SHARED_LIBS) 56 | set(static_default OFF) 57 | else() 58 | set(static_default ON) 59 | endif() 60 | 61 | option(BUILD_STATIC_DEPS "Build all dependencies statically rather than trying to link to them on the system" ${static_default}) 62 | option(STATIC_BUNDLE "Build a single static .a containing everything (both code and dependencies)" ${static_default}) 63 | 64 | if(BUILD_SHARED_LIBS OR libsession_IS_TOPLEVEL_PROJECT) 65 | set(install_default ON) 66 | else() 67 | set(install_default OFF) 68 | endif() 69 | 70 | option(LIBSESSION_INSTALL "Install libsession-util libraries and headers to cmake install target; defaults to ON if BUILD_SHARED_LIBS is enabled or when building the top-level project" ${install_default}) 71 | 72 | if(MINGW OR ANDROID OR IOS) # OR STATIC_BUNDLE) 73 | set(use_lto_default OFF) 74 | else() 75 | set(use_lto_default ON) 76 | endif() 77 | 78 | option(WARNINGS_AS_ERRORS "Treat all compiler warnings as errors" OFF) 79 | 80 | option(STATIC_LIBSTD "Statically link libstdc++/libgcc" ${default_static_libstd}) 81 | 82 | option(USE_LTO "Use Link-Time Optimization" ${use_lto_default}) 83 | 84 | # Provide this as an option for now because GMP and iOS are sometimes unhappy with each other. 85 | option(ENABLE_ONIONREQ "Build with onion request functionality" ON) 86 | 87 | if(USE_LTO) 88 | include(CheckIPOSupported) 89 | check_ipo_supported(RESULT IPO_ENABLED OUTPUT ipo_error) 90 | if(IPO_ENABLED) 91 | message(STATUS "LTO enabled") 92 | else() 93 | message(WARNING "LTO not supported by compiler: ${ipo_error}") 94 | endif() 95 | else() 96 | message(STATUS "LTO disabled") 97 | set(IPO_ENABLED OFF) 98 | endif() 99 | 100 | if(IPO_ENABLED AND NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) 101 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) 102 | endif() 103 | 104 | if(STATIC_LIBSTD) 105 | add_link_options(-static-libstdc++) 106 | if(NOT CMAKE_CXX_COMPILER_ID MATCHES Clang) 107 | add_link_options(-static-libgcc) 108 | endif() 109 | if(MINGW) 110 | add_link_options(-static -lwinpthread) 111 | endif() 112 | endif() 113 | 114 | include(AddStaticBundleLib) 115 | 116 | # Always build PIC 117 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 118 | 119 | 120 | add_subdirectory(external) 121 | add_subdirectory(src) 122 | add_subdirectory(proto) 123 | 124 | if (BUILD_STATIC_DEPS) 125 | include(StaticBuild) 126 | endif() 127 | 128 | if(STATIC_BUNDLE) 129 | 130 | include(combine_archives) 131 | 132 | combine_archives(session-util libsession-static-bundle "${LIBSESSION_STATIC_BUNDLE_LIBS}") 133 | set(lib_folder lib) 134 | if(IOS) 135 | set(lib_folder "${lib_folder}-${ARCH}") 136 | endif() 137 | 138 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libsession-util.a 139 | ARCHIVE DESTINATION ${lib_folder}) 140 | endif() 141 | 142 | 143 | option(WITH_TESTS "Enable unit tests" ${libsession_IS_TOPLEVEL_PROJECT}) 144 | if(WITH_TESTS) 145 | add_subdirectory(tests) 146 | endif() 147 | 148 | 149 | if(LIBSESSION_INSTALL) 150 | 151 | install( 152 | TARGETS ${libsession_export_targets} 153 | EXPORT libsessionConfig 154 | DESTINATION ${CMAKE_INSTALL_LIBDIR} 155 | ) 156 | 157 | install(DIRECTORY include/session DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 158 | PATTERN *.h PATTERN *.hpp) 159 | 160 | set(libsession_target_links) 161 | foreach(tgt ${libsession_export_targets}) 162 | set(libsession_target_links "${libsession_target_links} -lsession-${tgt}") 163 | endforeach() 164 | configure_file(libsession-util.pc.in libsession-util.pc @ONLY) 165 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libsession-util.pc 166 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig 167 | ) 168 | 169 | endif() 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Repository Deprecated 2 | 3 | Ongoing maintenance and development of this library is in the [Session Technology Foundation 4 | fork](https://github.com/session-foundation/libsession-util). For more details, see announcements 5 | from [Session](https://getsession.org/blog/introducing-the-session-technology-foundation) and the 6 | [OPTF](https://optf.ngo/blog/the-optf-and-session) regarding handing over the stewardship of the 7 | Session Project to the [Session Technology Foundation](https://session.foundation), a Swiss-based 8 | foundation dedicated to advancing digital rights and innovation. 9 | 10 | # Session utility library 11 | 12 | ## Docs 13 | 14 | C Library: https://api.oxen.io/libsession-util-c/#/ 15 | 16 | C++ Library: https://api.oxen.io/libsession-util-cpp/#/ 17 | -------------------------------------------------------------------------------- /cmake/AddStaticBundleLib.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(LIBSESSION_STATIC_BUNDLE_LIBS "" CACHE INTERNAL "list of libs to go into the static bundle lib") 3 | 4 | # Call as: 5 | # 6 | # libsession_static_bundle(target [target2 ...]) 7 | # 8 | # to append the given target(s) to the list of libraries that will be combined to make the static 9 | # bundled libsession-util.a. 10 | function(libsession_static_bundle) 11 | list(APPEND LIBSESSION_STATIC_BUNDLE_LIBS "${ARGN}") 12 | list(REMOVE_DUPLICATES LIBSESSION_STATIC_BUNDLE_LIBS) 13 | set(LIBSESSION_STATIC_BUNDLE_LIBS "${LIBSESSION_STATIC_BUNDLE_LIBS}" CACHE INTERNAL "") 14 | endfunction() 15 | -------------------------------------------------------------------------------- /cmake/GenVersion.cmake: -------------------------------------------------------------------------------- 1 | # cmake script to generate a version file via a configure_file after determining the current git 2 | # commit and tagged status. 3 | # 4 | # The should be invoked via something like the following: 5 | # 6 | # find_package(Git REQUIRED) 7 | # add_custom_command( 8 | # OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/version.c" 9 | # COMMAND 10 | # "${CMAKE_COMMAND}" 11 | # "-DGIT=${GIT_EXECUTABLE}" 12 | # "-DPROJECT_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}" 13 | # "-DPROJECT_VERSION_MINOR=${PROJECT_VERSION_MINOR}" 14 | # "-DPROJECT_VERSION_PATCH=${PROJECT_VERSION_PATCH}" 15 | # "-DSRC=${CMAKE_CURRENT_SOURCE_DIR}/version.c.in" 16 | # "-DDEST=${CMAKE_CURRENT_BINARY_DIR}/version.c" 17 | # "-P" "${PROJECT_SOURCE_DIR}/cmake/GenVersion.cmake" 18 | # DEPENDS 19 | # "${CMAKE_CURRENT_SOURCE_DIR}/version.c.in" 20 | # "${PROJECT_SOURCE_DIR}/.git/index") 21 | # 22 | # to dynamically create build/.../version.c from src/.../version.c.in and have it get properly 23 | # recreated as part of the build whenever either version.c.in or the current git commit change. 24 | # 25 | 26 | 27 | execute_process( 28 | COMMAND "${GIT}" rev-parse --short HEAD 29 | RESULT_VARIABLE git_result 30 | OUTPUT_VARIABLE git_commit 31 | OUTPUT_STRIP_TRAILING_WHITESPACE) 32 | 33 | if(git_result) 34 | message(WARNING "Failed to get current git commit; setting version tag to 'unknown'") 35 | set(PROJECT_VERSION_TAG "unknown") 36 | else() 37 | message(STATUS "Setting version tag to current git commit ${git_commit}") 38 | 39 | execute_process(COMMAND "${GIT}" tag --list --points-at HEAD 40 | RESULT_VARIABLE git_result 41 | OUTPUT_VARIABLE git_tag 42 | OUTPUT_STRIP_TRAILING_WHITESPACE) 43 | 44 | if(git_tag) 45 | message(STATUS "${git_commit} is tagged (${git_tag}); tagging version as 'release'") 46 | set(vfull "v${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") 47 | set(PROJECT_VERSION_TAG "release") 48 | 49 | if (NOT git_tag STREQUAL "${vfull}") 50 | message(FATAL_ERROR "This commit is tagged, but the tag (${git_tag}) does not match the project version (${vfull})!") 51 | endif() 52 | else() 53 | message(STATUS "Did not find a git tag for ${git_commit}; tagging version with the commit hash") 54 | set(PROJECT_VERSION_TAG "${git_commit}") 55 | endif() 56 | endif() 57 | 58 | configure_file("${SRC}" "${DEST}" @ONLY) 59 | -------------------------------------------------------------------------------- /cmake/MacroEnsureOutOfSourceBuild.cmake: -------------------------------------------------------------------------------- 1 | # - MACRO_ENSURE_OUT_OF_SOURCE_BUILD() 2 | # MACRO_ENSURE_OUT_OF_SOURCE_BUILD() 3 | 4 | # Copyright (c) 2006, Alexander Neundorf, 5 | # 6 | # Redistribution and use is allowed according to the terms of the BSD license: 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions 10 | # are met: 11 | # 12 | # 1. Redistributions of source code must retain the copyright 13 | # notice, this list of conditions and the following disclaimer. 14 | # 2. Redistributions in binary form must reproduce the copyright 15 | # notice, this list of conditions and the following disclaimer in the 16 | # documentation and/or other materials provided with the distribution. 17 | # 3. The name of the author may not be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 21 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22 | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 23 | # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 25 | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | macro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD _errorMessage) 32 | 33 | string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" _insource) 34 | if (_insource) 35 | message(SEND_ERROR "${_errorMessage}") 36 | message(FATAL_ERROR "Remove the file CMakeCache.txt in ${CMAKE_SOURCE_DIR} first.") 37 | endif (_insource) 38 | 39 | endmacro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD) 40 | -------------------------------------------------------------------------------- /cmake/combine_archives.cmake: -------------------------------------------------------------------------------- 1 | function(combine_archives output_archive dep_target) 2 | set(FULL_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/lib${output_archive}.a) 3 | 4 | if(NOT APPLE) 5 | set(mri_file ${CMAKE_CURRENT_BINARY_DIR}/${output_archive}.mri) 6 | set(mri_content "create ${FULL_OUTPUT_PATH}\n") 7 | foreach(in_archive ${ARGN}) 8 | string(APPEND mri_content "addlib $\n") 9 | endforeach() 10 | string(APPEND mri_content "save\nend\n") 11 | file(GENERATE OUTPUT ${mri_file} CONTENT "${mri_content}") 12 | 13 | add_custom_command( 14 | OUTPUT ${FULL_OUTPUT_PATH} 15 | DEPENDS ${mri_file} ${ARGN} 16 | COMMAND ar -M < ${mri_file}) 17 | else() 18 | set(merge_libs) 19 | foreach(in_archive ${ARGN}) 20 | list(APPEND merge_libs $) 21 | endforeach() 22 | add_custom_command( 23 | OUTPUT ${FULL_OUTPUT_PATH} 24 | DEPENDS ${mri_file} ${ARGN} 25 | COMMAND /usr/bin/libtool -static -o ${FULL_OUTPUT_PATH} ${merge_libs}) 26 | endif() 27 | add_custom_target(${output_archive} DEPENDS ${FULL_OUTPUT_PATH}) 28 | endfunction(combine_archives) 29 | -------------------------------------------------------------------------------- /cmake/mingw-i686-toolchain.cmake: -------------------------------------------------------------------------------- 1 | if(NOT CMAKE_HOST_WIN32) 2 | set(CMAKE_SYSTEM_NAME Windows) 3 | endif() 4 | 5 | set(CROSS_TARGET i686-w64-mingw32) 6 | set(ARCH_TRIPLET ${CROSS_TARGET}) 7 | set(CMAKE_C_COMPILER ${CROSS_TARGET}-gcc-posix) 8 | set(CMAKE_CXX_COMPILER ${CROSS_TARGET}-g++-posix) 9 | set(CMAKE_RC_COMPILER ${CROSS_TARGET}-windres) 10 | 11 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 12 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 13 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 14 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 15 | -------------------------------------------------------------------------------- /cmake/mingw-x86-64-toolchain.cmake: -------------------------------------------------------------------------------- 1 | if(NOT CMAKE_HOST_WIN32) 2 | set(CMAKE_SYSTEM_NAME Windows) 3 | endif() 4 | 5 | set(CROSS_TARGET x86_64-w64-mingw32) 6 | set(ARCH_TRIPLET ${CROSS_TARGET}) 7 | set(CMAKE_C_COMPILER ${CROSS_TARGET}-gcc-posix) 8 | set(CMAKE_CXX_COMPILER ${CROSS_TARGET}-g++-posix) 9 | set(CMAKE_RC_COMPILER ${CROSS_TARGET}-windres) 10 | 11 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 12 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 13 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 14 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 15 | -------------------------------------------------------------------------------- /docs/api/.gitignore: -------------------------------------------------------------------------------- 1 | api/* 2 | api 3 | -------------------------------------------------------------------------------- /docs/api/Makefile: -------------------------------------------------------------------------------- 1 | H_FILES = $(wildcard ../../include/session/config/*.h ../../include/session/config/*/*.h) 2 | HPP_FILES = $(wildcard ../../include/session/config/*.hpp ../../include/session/config/*/*.hpp) 3 | 4 | .PHONY: all 5 | all: h-docs hpp-docs 6 | 7 | .PHONY: hpp-docs 8 | hpp-docs: 9 | ./make-docs.sh libsession-util-cpp $(HPP_FILES) 10 | 11 | .PHONY: h-docs 12 | h-docs: 13 | ./make-docs.sh libsession-util-c $(H_FILES) 14 | 15 | .PHONY: run-c 16 | run-c: 17 | docsify serve libsession-util-c 18 | 19 | .PHONY: run-cpp 20 | run-cpp: 21 | docsify serve libsession-util-cpp 22 | 23 | 24 | .PHONY: clean 25 | clean: 26 | rm -rf ./libsession-util-c ./libsession-util-cpp 27 | -------------------------------------------------------------------------------- /docs/api/make-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The following npm packages must be installed 4 | # docsify-cli docsify-themeable docsify-katex@1.4.4 katex marked@4 5 | 6 | # To customise the theme see: 7 | # https://jhildenbiddle.github.io/docsify-themeable/#/customization 8 | 9 | set -e 10 | 11 | if [ "$(basename $(pwd))" != "api" ]; then 12 | echo "Error: you must run this from the docs/api directory" >&2 13 | exit 1 14 | fi 15 | 16 | destdir="$1" 17 | shift 18 | 19 | if [ -d "$destdir" ]; then 20 | rm -rf "$destdir" 21 | fi 22 | 23 | npx docsify init --local "$destdir" 24 | 25 | rm -Rf "$destdir"/vendor/themes 26 | rm -f "$destdir"/README.md 27 | 28 | if [ -n "$NPM_PACKAGES" ]; then 29 | npm_dir="$NPM_PACKAGES/lib/node_modules" 30 | elif [ -n "$NODE_PATH" ]; then 31 | npm_dir="$NODE_PATH" 32 | elif [ -d "$HOME/node_modules" ]; then 33 | npm_dir="$HOME/node_modules" 34 | elif [ -d "/usr/local/lib/node_modules" ]; then 35 | npm_dir="/usr/local/lib/node_modules" 36 | else 37 | echo "Can't determine your node_modules path; set NPM_PACKAGES or NODE_PATH appropriately" >&2 38 | exit 1 39 | fi 40 | 41 | cp $npm_dir/docsify/lib/plugins/search.min.js "$destdir"/vendor 42 | cp $npm_dir/prismjs/components/prism-{json,python,http}.min.js "$destdir"/vendor 43 | cp $npm_dir/docsify-themeable/dist/css/theme-simple.css "$destdir"/vendor 44 | cp $npm_dir/docsify-themeable/dist/css/theme-simple-dark.css "$destdir"/vendor 45 | cp $npm_dir/docsify-themeable/dist/js/docsify-themeable.min.js "$destdir"/vendor 46 | cp $npm_dir/marked/marked.min.js "$destdir"/vendor 47 | cp $npm_dir/katex/dist/katex.min.js "$destdir"/vendor 48 | cp $npm_dir/katex/dist/katex.min.css "$destdir"/vendor 49 | cp -R $npm_dir/katex/dist/fonts "$destdir"/vendor 50 | cp $npm_dir/docsify-katex/dist/docsify-katex.js "$destdir"/vendor 51 | 52 | ./api-to-markdown.py --out="$destdir" "$@" 53 | 54 | perl -ni -e ' 55 | BEGIN { $first = 0; } 56 | if (m{^\s*\s*$}) { 57 | if (not $first) { 58 | $first = false; 59 | print qq{ 60 | \n}; 73 | } 74 | } else { 75 | s{.*}{Libsession Utils API}; 76 | s{(name="description" content=)"[^"]*"}{$1"libsession-util function documentation"}; 77 | s{^\s*\s*$}{}; 78 | if (m{^\s*}) { 79 | print qq{ 80 | 81 | 82 | 83 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | \n}; 96 | } 97 | print; 98 | }' "$destdir"/index.html 99 | -------------------------------------------------------------------------------- /docs/api/static/index.md: -------------------------------------------------------------------------------- 1 | # Libsession Util API Functions 2 | 3 | These pages describe the available API functions available from libsession util library. These 4 | endpoints are used for querying and modifying the config struct handled by libsession. 5 | -------------------------------------------------------------------------------- /docs/api/static/sidebar.md: -------------------------------------------------------------------------------- 1 | - [Base](base.md) 2 | - [Community](community.md) 3 | - [Contacts](contacts.md) 4 | - [Convo Info Volatile](convo_info_volatile.md) 5 | - [Encrypt](encrypt.md) 6 | - [Error](error.md) 7 | - [Groups](groups.md) 8 | - [User Groups](user_groups.md) 9 | - [User Profile](user_profile.md) 10 | - [Utils](util.md) 11 | -------------------------------------------------------------------------------- /include/session/blinding.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | #include "export.h" 10 | #include "platform.h" 11 | 12 | /// API: crypto/session_blind15_key_pair 13 | /// 14 | /// This function attempts to generate a blind15 key pair. 15 | /// 16 | /// Inputs: 17 | /// - `ed25519_seckey` -- [in] the Ed25519 private key of the sender (64 bytes). 18 | /// - `server_pk` -- [in] the public key of the open group server to generate the 19 | /// blinded id for (32 bytes). 20 | /// - `blinded_pk_out` -- [out] pointer to a buffer of at least 32 bytes where the blinded_pk will 21 | /// be written if generation was successful. 22 | /// - `blinded_sk_out` -- [out] pointer to a buffer of at least 32 bytes where the blinded_sk will 23 | /// be written if generation was successful. 24 | /// 25 | /// Outputs: 26 | /// - `bool` -- True if the key was successfully generated, false if generation failed. 27 | LIBSESSION_EXPORT bool session_blind15_key_pair( 28 | const unsigned char* ed25519_seckey, /* 64 bytes */ 29 | const unsigned char* server_pk, /* 32 bytes */ 30 | unsigned char* blinded_pk_out, /* 32 byte output buffer */ 31 | unsigned char* blinded_sk_out /* 32 byte output buffer */); 32 | 33 | /// API: crypto/session_blind25_key_pair 34 | /// 35 | /// This function attempts to generate a blind25 key pair. 36 | /// 37 | /// Inputs: 38 | /// - `ed25519_seckey` -- [in] the Ed25519 private key of the sender (64 bytes). 39 | /// - `server_pk` -- [in] the public key of the open group server to generate the 40 | /// blinded id for (32 bytes). 41 | /// - `blinded_pk_out` -- [out] pointer to a buffer of at least 32 bytes where the blinded_pk will 42 | /// be written if generation was successful. 43 | /// - `blinded_sk_out` -- [out] pointer to a buffer of at least 32 bytes where the blinded_sk will 44 | /// be written if generation was successful. 45 | /// 46 | /// Outputs: 47 | /// - `bool` -- True if the key was successfully generated, false if generation failed. 48 | LIBSESSION_EXPORT bool session_blind25_key_pair( 49 | const unsigned char* ed25519_seckey, /* 64 bytes */ 50 | const unsigned char* server_pk, /* 32 bytes */ 51 | unsigned char* blinded_pk_out, /* 32 byte output buffer */ 52 | unsigned char* blinded_sk_out /* 32 byte output buffer */); 53 | 54 | /// API: crypto/session_blind_version_key_pair 55 | /// 56 | /// This function attempts to generate a blind-version key pair. 57 | /// 58 | /// Inputs: 59 | /// - `ed25519_seckey` -- [in] the Ed25519 private key of the user (64 bytes). 60 | /// - `blinded_pk_out` -- [out] pointer to a buffer of at least 32 bytes where the blinded_pk will 61 | /// be written if generation was successful. 62 | /// - `blinded_sk_out` -- [out] pointer to a buffer of at least 64 bytes where the blinded_sk will 63 | /// be written if generation was successful. 64 | /// 65 | /// Outputs: 66 | /// - `bool` -- True if the key was successfully generated, false if generation failed. 67 | LIBSESSION_EXPORT bool session_blind_version_key_pair( 68 | const unsigned char* ed25519_seckey, /* 64 bytes */ 69 | unsigned char* blinded_pk_out, /* 32 byte output buffer */ 70 | unsigned char* blinded_sk_out /* 64 byte output buffer */); 71 | 72 | /// API: crypto/session_blind15_sign 73 | /// 74 | /// This function attempts to generate a signature for a message using a blind15 private key. 75 | /// 76 | /// Inputs: 77 | /// - `ed25519_seckey` -- [in] the Ed25519 private key of the sender (64 bytes). 78 | /// - `server_pk` -- [in] the public key of the open group server to generate the 79 | /// blinded id for (32 bytes). 80 | /// - `msg` -- [in] Pointer to a data buffer containing the message to generate a signature for. 81 | /// - `msg_len` -- [in] Length of `msg` 82 | /// - `blinded_sig_out` -- [out] pointer to a buffer of at least 64 bytes where the signature will 83 | /// be written if generation was successful. 84 | /// 85 | /// Outputs: 86 | /// - `bool` -- True if the signature was successfully generated, false if generation failed. 87 | LIBSESSION_EXPORT bool session_blind15_sign( 88 | const unsigned char* ed25519_seckey, /* 64 bytes */ 89 | const unsigned char* server_pk, /* 32 bytes */ 90 | const unsigned char* msg, 91 | size_t msg_len, 92 | unsigned char* blinded_sig_out /* 64 byte output buffer */); 93 | 94 | /// API: crypto/session_blind25_sign 95 | /// 96 | /// This function attempts to generate a signature for a message using a blind25 private key. 97 | /// 98 | /// Inputs: 99 | /// - `ed25519_seckey` -- [in] the Ed25519 private key of the sender (64 bytes). 100 | /// - `server_pk` -- [in] the public key of the open group server to generate the 101 | /// blinded id for (32 bytes). 102 | /// - `msg` -- [in] Pointer to a data buffer containing the message to generate a signature for. 103 | /// - `msg_len` -- [in] Length of `msg` 104 | /// - `blinded_sig_out` -- [out] pointer to a buffer of at least 64 bytes where the signature will 105 | /// be written if generation was successful. 106 | /// 107 | /// Outputs: 108 | /// - `bool` -- True if the signature was successfully generated, false if generation failed. 109 | LIBSESSION_EXPORT bool session_blind25_sign( 110 | const unsigned char* ed25519_seckey, /* 64 bytes */ 111 | const unsigned char* server_pk, /* 32 bytes */ 112 | const unsigned char* msg, 113 | size_t msg_len, 114 | unsigned char* blinded_sig_out /* 64 byte output buffer */); 115 | 116 | /// Computes a verifiable version-blinded signature that validates with the version-blinded pubkey 117 | /// that would be returned from blind_version_key_pair. 118 | /// 119 | /// Takes the Ed25519 secret key (64 bytes), platform and unix timestamp. Returns a version-blinded 120 | /// signature. 121 | LIBSESSION_EXPORT bool session_blind_version_sign( 122 | const unsigned char* ed25519_seckey, /* 64 bytes */ 123 | CLIENT_PLATFORM platform, 124 | size_t timestamp, 125 | unsigned char* blinded_sig_out /* 64 byte output buffer */); 126 | 127 | /// API: crypto/session_blind25_sign 128 | /// 129 | /// This function attempts to generate a signature for a message using a blind25 private key. 130 | /// 131 | /// Inputs: 132 | /// - `session_id` -- [in] the session_id to compare (66 bytes with a 05 prefix). 133 | /// - `blinded_id` -- [in] the blinded_id to compare, can be either 15 or 25 blinded (66 bytes). 134 | /// - `server_pk` -- [in] the public key of the open group server to the blinded id came from (64 135 | /// bytes). 136 | /// 137 | /// Outputs: 138 | /// - `bool` -- True if the session_id matches the blinded_id, false if not. 139 | LIBSESSION_EXPORT bool session_id_matches_blinded_id( 140 | const char* session_id, /* 66 bytes */ 141 | const char* blinded_id, /* 66 bytes */ 142 | const char* server_pk /* 64 bytes */); 143 | 144 | #ifdef __cplusplus 145 | } 146 | #endif 147 | -------------------------------------------------------------------------------- /include/session/bt_merge.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #ifndef NDEBUG 6 | #include 7 | #endif 8 | 9 | namespace session::bt { 10 | 11 | using oxenc::bt_dict; 12 | using oxenc::bt_list; 13 | 14 | /// Merges two bt dicts together: the returned dict includes all keys in a or b. Keys in *both* 15 | /// dicts get their value from `a`, otherwise the value is that of the dict that contains the key. 16 | bt_dict merge(const bt_dict& a, const bt_dict& b); 17 | 18 | /// Merges two ordered bt_lists together using a predicate to determine order. The input lists must 19 | /// be sorted to begin with. `cmp` must be callable with a pair of `const bt_value&` arguments and 20 | /// must return true if the first argument should be considered less than the second argument. By 21 | /// default this skips elements from b that compare equal to a value of a, but you can include all 22 | /// the duplicates by specifying the `duplicates` parameter as true. 23 | template 24 | bt_list merge_sorted(const bt_list& a, const bt_list& b, Compare cmp, bool duplicates = false) { 25 | bt_list result; 26 | auto it_a = a.begin(); 27 | auto it_b = b.begin(); 28 | 29 | assert(std::is_sorted(it_a, a.end(), cmp)); 30 | assert(std::is_sorted(it_b, b.end(), cmp)); 31 | 32 | if (duplicates) { 33 | while (it_a != a.end() && it_b != b.end()) { 34 | if (!cmp(*it_a, *it_b)) // *b <= *a 35 | result.push_back(*it_b++); 36 | else // *a < *b 37 | result.push_back(*it_a++); 38 | } 39 | } else { 40 | while (it_a != a.end() && it_b != b.end()) { 41 | if (cmp(*it_b, *it_a)) // *b < *a 42 | result.push_back(*it_b++); 43 | else if (cmp(*it_a, *it_b)) // *a < *b 44 | result.push_back(*it_a++); 45 | else // *a == *b 46 | ++it_b; // skip it 47 | } 48 | } 49 | 50 | if (it_a != a.end()) 51 | result.insert(result.end(), it_a, a.end()); 52 | else if (it_b != b.end()) 53 | result.insert(result.end(), it_b, b.end()); 54 | 55 | return result; 56 | } 57 | 58 | } // namespace session::bt 59 | -------------------------------------------------------------------------------- /include/session/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | typedef int64_t seqno_t; 10 | 11 | #ifdef __cplusplus 12 | } 13 | #endif 14 | -------------------------------------------------------------------------------- /include/session/config/community.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | #include "../export.h" 11 | 12 | // Maximum string length of a community base URL 13 | LIBSESSION_EXPORT extern const size_t COMMUNITY_BASE_URL_MAX_LENGTH; 14 | 15 | // Maximum string length of a community room token 16 | LIBSESSION_EXPORT extern const size_t COMMUNITY_ROOM_MAX_LENGTH; 17 | 18 | // Maximum string length of a full URL as produced by the community_make_full_url() function. 19 | // Unlike the above constants, this *includes* space for a NULL string terminator. 20 | LIBSESSION_EXPORT extern const size_t COMMUNITY_FULL_URL_MAX_LENGTH; 21 | 22 | /// API: community/community_parse_full_url 23 | /// 24 | /// Parses a community URL. Writes the canonical base url, room token, and pubkey bytes into the 25 | /// given pointers. base_url must be at least BASE_URL_MAX_LENGTH+1; room must be at least 26 | /// ROOM_MAX_LENGTH+1; and pubkey must be (at least) 32 bytes. 27 | /// 28 | /// Returns true if the url was parsed successfully, false if parsing failed (i.e. an invalid URL). 29 | /// 30 | /// Declaration: 31 | /// ```cpp 32 | /// BOOL community_parse_full_url( 33 | /// [in] const char* full_url, 34 | /// [out] char* base_url, 35 | /// [out] char* room_token, 36 | /// [out] unsigned char* pubkey 37 | /// ); 38 | /// ``` 39 | /// 40 | /// Inputs: 41 | /// - `full_url` -- [in] Text of the url 42 | /// - `base_url` -- [out] Text of the base url 43 | /// - `room_token` -- [out] Binary of the the token 44 | /// - `pubkey` -- [out] Binary of the pubkey 45 | /// 46 | /// Outputs: 47 | /// 48 | /// - `bool` -- Whether the function succeeded or not 49 | LIBSESSION_EXPORT bool community_parse_full_url( 50 | const char* full_url, char* base_url, char* room_token, unsigned char* pubkey); 51 | 52 | /// API: community/community_parse_partial_url 53 | /// 54 | /// Similar to the above `community_parse_full_url`, but allows a URL to omit the pubkey. If no 55 | /// pubkey is found, `pubkey` is left unchanged and `has_pubkey` is set to false; otherwise `pubkey` 56 | /// is written and `has_pubkey` is set to true. `pubkey` may be set to NULL, in which case it is 57 | /// never written. `has_pubkey` may be NULL in which case it is not set (typically both pubkey 58 | /// arguments would be null for cases where you don't care at all about the pubkey). 59 | /// 60 | /// Declaration: 61 | /// ```cpp 62 | /// BOOL community_parse_partial_url( 63 | /// [in] const char* full_url, 64 | /// [out] char* base_url, 65 | /// [out] char* room_token, 66 | /// [out] unsigned char* pubkey, 67 | /// [out] bool* has_pubkey 68 | /// ); 69 | /// ``` 70 | /// 71 | /// Inputs: 72 | /// - `full_url` -- [in] Text of the url 73 | /// - `base_url` -- [out] Text of the url 74 | /// - `room_token` -- [out] Binary of the the token 75 | /// - `pubkey` -- [out] Binary of the pubkey 76 | /// - `has_pubkey` -- [out] Will be true if the full url has a pubkey 77 | /// 78 | /// Outputs: 79 | /// - `bool` -- true if successful 80 | LIBSESSION_EXPORT bool community_parse_partial_url( 81 | const char* full_url, 82 | char* base_url, 83 | char* room_token, 84 | unsigned char* pubkey, 85 | bool* has_pubkey); 86 | 87 | /// API: community/community_make_full_url 88 | /// 89 | /// Produces a standard full URL from a given base_url (c string), room token (c string), and pubkey 90 | /// (fixed-length 32 byte buffer). The full URL is written to `full_url`, which must be at least 91 | /// COMMUNITY_FULL_URL_MAX_LENGTH in size. 92 | /// 93 | /// Declaration: 94 | /// ```cpp 95 | /// VOID community_make_full_url( 96 | /// [in] const char* base_url, 97 | /// [in] const char* room_token, 98 | /// [in] const unsigned char* pubkey, 99 | /// [out] char* full_url 100 | /// ); 101 | /// ``` 102 | /// 103 | /// Inputs: 104 | /// - `base_url` -- [in] Text of the url 105 | /// - `room` -- [in] Text of the the token 106 | /// - `pubkey` -- [in] Binary of the pubkey, 32 bytes 107 | /// - `full_url` -- [out] Text of the url 108 | LIBSESSION_EXPORT void community_make_full_url( 109 | const char* base_url, const char* room, const unsigned char* pubkey, char* full_url); 110 | 111 | #ifdef __cplusplus 112 | } 113 | #endif 114 | -------------------------------------------------------------------------------- /include/session/config/encrypt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | #include "../export.h" 10 | 11 | /// API: encrypt/config_encrypt 12 | /// 13 | /// Wrapper around session::config::encrypt. message and key_base are binary: message has the 14 | /// length provided, key_base must be exactly 32 bytes. domain is a c string. Returns a newly 15 | /// allocated buffer containing the encrypted data, and sets the data's length into 16 | /// `ciphertext_size`. It is the caller's responsibility to `free()` the returned buffer! 17 | /// 18 | /// Declaration: 19 | /// ```cpp 20 | /// UNSIGNED CHAR* config_encrypt( 21 | /// [in] const unsigned char* message, 22 | /// [in] size_t mlen, 23 | /// [in] const unsigned char* key_base, 24 | /// [in] const char* domain, 25 | /// [out] size_t* ciphertext_size 26 | /// ); 27 | /// ``` 28 | /// 29 | /// Inputs: 30 | /// - `message` -- [in] The message to encrypted in binary 31 | /// - `mlen` -- [in] Length of the message provided 32 | /// - `key_base` -- [in] Key, must be binary 33 | /// - `domain` -- [in] Text 34 | /// - `ciphertext_size` -- [out] will contain the size of the returned ciphertext 35 | /// 36 | /// Outputs: 37 | /// - `unsigned char*` -- ciphertext, will be nullptr on error 38 | LIBSESSION_EXPORT unsigned char* config_encrypt( 39 | const unsigned char* message, 40 | size_t mlen, 41 | const unsigned char* key_base, 42 | const char* domain, 43 | size_t* ciphertext_size); 44 | 45 | /// API: encrypt/config_decrypt 46 | /// 47 | /// Wrapper around session::config::decrypt. ciphertext and key_base are binary: ciphertext has the 48 | /// length provided, key_base must be exactly 32 bytes. domain is a c string. Returns a newly 49 | /// allocated buffer containing the decrypted data, and sets the data's length into 50 | /// `plaintext_size`. It is the caller's responsibility to `free()` the returned buffer! 51 | /// 52 | /// Declaration: 53 | /// ```cpp 54 | /// UNSIGNED CHAR* config_decrypt( 55 | /// [in] const unsigned char* ciphertext, 56 | /// [in] size_t clen, 57 | /// [in] const unsigned char* key_base, 58 | /// [in] const char* domain, 59 | /// [out] size_t* plaintext_size 60 | /// ); 61 | /// ``` 62 | /// 63 | /// Inputs: 64 | /// - `ciphertext` -- [in] the message to be decrypted in binary 65 | /// - `clen` -- [in] length of the message provided 66 | /// - `key_base` -- [in] key, must be binary 67 | /// - `domain` -- [in] text 68 | /// - `plaintext_size` -- [out] will contain the size of the returned plaintext 69 | /// 70 | /// Outputs: 71 | /// - `unsigned char*` -- decrypted message, will be nullptr on error 72 | LIBSESSION_EXPORT unsigned char* config_decrypt( 73 | const unsigned char* ciphertext, 74 | size_t clen, 75 | const unsigned char* key_base, 76 | const char* domain, 77 | size_t* plaintext_size); 78 | 79 | /// API: encrypt/config_padded_size 80 | /// 81 | /// Returns the amount of padding needed for a plaintext of size s with encryption overhead 82 | /// `overhead`. 83 | /// 84 | /// Declaration: 85 | /// ```cpp 86 | /// SIZE_T config_padded_size( 87 | /// [in] size_t s, 88 | /// [in] size_t overhead 89 | /// ); 90 | /// ``` 91 | /// 92 | /// Inputs: 93 | /// - `s` -- [in] unsigned integer of the size of the plaintext 94 | /// - `overhead` -- [in] unsigned integer of the desired overhead 95 | /// 96 | /// Outputs: 97 | /// - `size_t` -- Unsigned integer of the amount of padding necessary 98 | LIBSESSION_EXPORT size_t config_padded_size(size_t s, size_t overhead); 99 | 100 | #ifdef __cplusplus 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /include/session/config/encrypt.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../types.hpp" 6 | 7 | namespace session::config { 8 | 9 | /// API: encrypt/encrypt 10 | /// 11 | /// Encrypts a config message using XChaCha20-Poly1305, using a blake2b keyed hash of the message 12 | /// for the nonce (rather than pure random) so that different clients will encrypt the same data to 13 | /// the same encrypted value (thus allowing for server-side deduplication of identical messages). 14 | /// 15 | /// `key_base` must be 32 bytes. This value is a fixed key that all clients that might receive this 16 | /// message can calculate independently (for instance a value derived from a secret key, or a shared 17 | /// random key). This key will be hashed with the message size and domain suffix (see below) to 18 | /// determine the actual encryption key. 19 | /// 20 | /// `domain` is a short string (1-24 chars) used for the keyed hash. Typically this is the type of 21 | /// config, e.g. "closed-group" or "contacts". The full key will be 22 | /// "session-config-encrypted-message-[domain]". This value is also used for the encrypted key (see 23 | /// above). 24 | /// 25 | /// The returned result will consist of encrypted data with authentication tag and appended nonce, 26 | /// suitable for being passed to decrypt() to authenticate and decrypt. 27 | /// 28 | /// Throw std::invalid_argument on bad input (i.e. from invalid key_base or domain). 29 | /// 30 | /// Inputs: 31 | /// - `message` -- message to encrypt 32 | /// - `key_base` -- Fixed key that all clients, must be 32 bytes. 33 | /// - `domain` -- short string for the keyed hash 34 | /// 35 | /// Outputs: 36 | /// - `ustring` -- Returns the encrypted message bytes 37 | ustring encrypt(ustring_view message, ustring_view key_base, std::string_view domain); 38 | 39 | /// API: encrypt/encrypt_inplace 40 | /// 41 | /// Same as above `encrypt`, but modifies `message` in place. `message` gets encrypted plus has the 42 | /// extra data and nonce appended. 43 | /// 44 | /// Inputs: 45 | /// - `message` -- message to encrypt 46 | /// - `key_base` -- Fixed key that all clients, must be 32 bytes. 47 | /// - `domain` -- short string for the keyed hash 48 | void encrypt_inplace(ustring& message, ustring_view key_base, std::string_view domain); 49 | 50 | /// Constant amount of extra bytes required to be appended when encrypting. 51 | constexpr size_t ENCRYPT_DATA_OVERHEAD = 40; // ABYTES + NPUBBYTES 52 | 53 | /// Thrown if decrypt() fails. 54 | struct decrypt_error : std::runtime_error { 55 | using std::runtime_error::runtime_error; 56 | }; 57 | 58 | /// API: encrypt/decrypt 59 | /// 60 | /// Takes a value produced by `encrypt()` and decrypts it. `key_base` and `domain` must be the same 61 | /// given to encrypt or else decryption fails. Upon decryption failure a `decrypt_error` exception 62 | /// is thrown. 63 | /// 64 | /// Inputs: 65 | /// - `ciphertext` -- message to decrypt 66 | /// - `key_base` -- Fixed key that all clients, must be 32 bytes. 67 | /// - `domain` -- short string for the keyed hash 68 | /// 69 | /// Outputs: 70 | /// - `ustring` -- Returns the decrypt message bytes 71 | ustring decrypt(ustring_view ciphertext, ustring_view key_base, std::string_view domain); 72 | 73 | /// API: encrypt/decrypt_inplace 74 | /// 75 | /// Same as above `decrypt()`, but does in in-place. The string gets shortend to the plaintext 76 | /// after this call. 77 | /// 78 | /// Inputs: 79 | /// - `ciphertext` -- message to decrypt 80 | /// - `key_base` -- Fixed key that all clients, must be 32 bytes. 81 | /// - `domain` -- short string for the keyed hash 82 | void decrypt_inplace(ustring& ciphertext, ustring_view key_base, std::string_view domain); 83 | 84 | /// Returns the target size of the message with padding, assuming an additional `overhead` bytes of 85 | /// overhead (e.g. from encrypt() overhead) will be appended. Will always return a value >= s + 86 | /// overhead. 87 | /// 88 | /// Padding increments we use: 256 byte increments up to 5120; 1024 byte increments up to 20480, 89 | /// 2048 increments up to 40960, then 5120 from there up. 90 | inline constexpr size_t padded_size(size_t s, size_t overhead = ENCRYPT_DATA_OVERHEAD) { 91 | size_t s2 = s + overhead; 92 | size_t chunk = s2 < 5120 ? 256 : s2 < 20480 ? 1024 : s2 < 40960 ? 2048 : 5120; 93 | return (s2 + chunk - 1) / chunk * chunk - overhead; 94 | } 95 | 96 | /// API: encrypt/pad_message 97 | /// 98 | /// Inserts null byte padding to the beginning of a message to make the final message size granular. 99 | /// See the above function for the sizes. 100 | /// 101 | /// \param data - the data; this is modified in place. 102 | /// \param overhead - 103 | /// 104 | /// Inputs: 105 | /// - `data` -- the data; this is modified in place 106 | /// - `overhead` -- encryption overhead to account for to reach the desired padded size. The 107 | /// default, if omitted, is the space used by the `encrypt()` function defined above. 108 | void pad_message(ustring& data, size_t overhead = ENCRYPT_DATA_OVERHEAD); 109 | 110 | } // namespace session::config 111 | -------------------------------------------------------------------------------- /include/session/config/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include "../export.h" 8 | 9 | enum config_error { 10 | /// Value returned for no error 11 | SESSION_ERR_NONE = 0, 12 | /// Error indicating that initialization failed because the dumped data being loaded is invalid. 13 | SESSION_ERR_INVALID_DUMP = 1, 14 | /// Error indicated a bad value, e.g. if trying to set something invalid in a config field. 15 | SESSION_ERR_BAD_VALUE = 2, 16 | }; 17 | 18 | /// API: error/config_errstr 19 | /// 20 | /// Returns a generic string for a given integer error code as returned by some functions. Depending 21 | /// on the call, a more details error string may be available in the config_object's `last_error` 22 | /// field. 23 | /// 24 | /// Declaration: 25 | /// ```cpp 26 | /// CONST CHAR* config_errstr( 27 | /// [in] int err 28 | /// ); 29 | /// ``` 30 | /// 31 | /// Inputs: 32 | /// - `err` -- [in] Integer of the error code 33 | /// 34 | /// Outputs: 35 | /// - `const char*` -- text of the error string 36 | LIBSESSION_EXPORT const char* config_errstr(int err); 37 | 38 | #ifdef __cplusplus 39 | } // extern "C" 40 | #endif 41 | -------------------------------------------------------------------------------- /include/session/config/expiring.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef enum CONVO_EXPIRATION_MODE { 4 | CONVO_EXPIRATION_NONE = 0, 5 | CONVO_EXPIRATION_AFTER_SEND = 1, 6 | CONVO_EXPIRATION_AFTER_READ = 2, 7 | } CONVO_EXPIRATION_MODE; 8 | -------------------------------------------------------------------------------- /include/session/config/expiring.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace session::config { 5 | 6 | enum class expiration_mode : int8_t { none = 0, after_send = 1, after_read = 2 }; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /include/session/config/namespaces.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace session::config { 6 | 7 | enum class Namespace : std::int16_t { 8 | UserProfile = 2, 9 | Contacts = 3, 10 | ConvoInfoVolatile = 4, 11 | UserGroups = 5, 12 | 13 | // Messages sent to a closed group: 14 | GroupMessages = 11, 15 | // Groups config namespaces (i.e. for shared config of the group itself, not one user's group 16 | // settings) 17 | GroupKeys = 12, 18 | GroupInfo = 13, 19 | GroupMembers = 14, 20 | }; 21 | 22 | } // namespace session::config 23 | -------------------------------------------------------------------------------- /include/session/config/notify.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef enum CONVO_NOTIFY_MODE { 4 | CONVO_NOTIFY_DEFAULT = 0, 5 | CONVO_NOTIFY_ALL = 1, 6 | CONVO_NOTIFY_DISABLED = 2, 7 | CONVO_NOTIFY_MENTIONS_ONLY = 3, 8 | } CONVO_NOTIFY_MODE; 9 | -------------------------------------------------------------------------------- /include/session/config/notify.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace session::config { 4 | 5 | enum class notify_mode { 6 | defaulted = 0, 7 | all = 1, 8 | disabled = 2, 9 | mentions_only = 3, // Only for groups; for DMs this becomes `all` 10 | }; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /include/session/config/profile_pic.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | #include "../export.h" 10 | 11 | // Maximum length of the profile pic URL (not including the null terminator) 12 | LIBSESSION_EXPORT extern const size_t PROFILE_PIC_MAX_URL_LENGTH; 13 | 14 | typedef struct user_profile_pic { 15 | // Null-terminated C string containing the uploaded URL of the pic. Will be length 0 if there 16 | // is no profile pic. 17 | char url[224]; 18 | // The profile pic decryption key, in bytes. This is a byte buffer of length 32, *not* a 19 | // null-terminated C string. This is only valid when there is a url (i.e. url has strlen > 0). 20 | unsigned char key[32]; 21 | } user_profile_pic; 22 | 23 | #ifdef __cplusplus 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /include/session/config/profile_pic.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "session/types.hpp" 6 | 7 | namespace session::config { 8 | 9 | // Profile pic info. 10 | struct profile_pic { 11 | static constexpr size_t MAX_URL_LENGTH = 223; 12 | 13 | std::string url; 14 | ustring key; 15 | 16 | static void check_key(ustring_view key) { 17 | if (!(key.empty() || key.size() == 32)) 18 | throw std::invalid_argument{"Invalid profile pic key: 32 bytes required"}; 19 | } 20 | 21 | // Default constructor, makes an empty profile pic 22 | profile_pic() = default; 23 | 24 | // Constructs from a URL and key. Key must be empty or 32 bytes. 25 | profile_pic(std::string_view url, ustring_view key) : url{url}, key{key} { 26 | check_key(this->key); 27 | } 28 | 29 | // Constructs from a string/ustring pair moved into the constructor 30 | profile_pic(std::string&& url, ustring&& key) : url{std::move(url)}, key{std::move(key)} { 31 | check_key(this->key); 32 | } 33 | 34 | /// API: profile_pic/profile_pic::empty 35 | /// 36 | /// Returns true if either url or key are empty (or invalid) 37 | /// 38 | /// Inputs: None 39 | /// 40 | /// Outputs: 41 | /// - `bool` -- Returns true if either url or key are empty 42 | bool empty() const { return url.empty() || key.size() != 32; } 43 | 44 | /// API: profile_pic/profile_pic::clear 45 | /// 46 | /// Clears the current url/key, if set. This is just a shortcut for calling `.clear()` on each 47 | /// of them. 48 | /// 49 | /// Inputs: None 50 | void clear() { 51 | url.clear(); 52 | key.clear(); 53 | } 54 | 55 | // The object in boolean context is true if url and key are both set, i.e. the opposite of 56 | // `empty()`. 57 | explicit operator bool() const { return !empty(); } 58 | 59 | /// API: profile_pic/profile_pic::set_key 60 | /// 61 | /// Sets and validates the key. The key can be empty, or 32 bytes. This is almost the same as 62 | /// just setting `.key` directly, except that it will throw if the provided key is invalid (i.e. 63 | /// neither empty nor 32 bytes). 64 | /// 65 | /// Inputs: 66 | /// - `new_key` -- binary data of a new key to be set. Must be 32 bytes 67 | void set_key(ustring new_key) { 68 | check_key(new_key); 69 | key = std::move(new_key); 70 | } 71 | }; 72 | 73 | } // namespace session::config 74 | -------------------------------------------------------------------------------- /include/session/config/protos.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "namespaces.hpp" 4 | #include "session/util.hpp" 5 | 6 | namespace session::config::protos { 7 | 8 | /// API: config/protos::wrap_config 9 | /// 10 | /// Wraps a config message in endless layers of protobuf and unnecessary extra encryption and 11 | /// then more protobuf as required by older clients for older config message types. 12 | /// 13 | /// Inputs: 14 | /// - `ed25519_sk` a 32- or 64-byte, libsodium-style secret key value (if 32 then it is just the 15 | /// seed). 16 | /// - `data` the config data to wrap 17 | /// - `seqno` the seqno value of the data 18 | /// - `ns` the namespace of the config data 19 | /// 20 | /// Outputs: 21 | /// Returns the wrapped config. Will throw on serious errors (e.g. `ed25519_sk` or `ns` are 22 | /// invalid). 23 | ustring wrap_config( 24 | ustring_view ed25519_sk, ustring_view data, int64_t seqno, config::Namespace ns); 25 | 26 | /// API: config/protos::unwrap_config 27 | /// 28 | /// Unwraps a config message from endless layers of protobuf, extra encryption and then more 29 | /// protobuf as required by older clients for older config message types. 30 | /// 31 | /// Inputs: 32 | /// - `ed25519_sk` a 32- or 64-byte, libsodium-style secret key value (if 32 then it is just the 33 | /// seed). 34 | /// - `data` the incoming data that might be protobuf-wrapped 35 | /// 36 | /// Outputs: 37 | /// 38 | /// Returns the unwrapped, inner config value if this is a proper protobuf-wrapped message; throws 39 | /// std::runtime_error if it is not (thus most likely indicating that this is a raw config value). 40 | /// Throws a std::invalid_argument if the given ed25519_sk is invalid. (It is recommended that only 41 | /// the std::runtime_error is caught for detecting non-wrapped input as the invalid secret key is 42 | /// more serious). 43 | ustring unwrap_config(ustring_view ed25519_sk, ustring_view data, config::Namespace ns); 44 | 45 | } // namespace session::config::protos 46 | -------------------------------------------------------------------------------- /include/session/config/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | #include "../export.h" 10 | 11 | /// API: util/session_id_is_valid 12 | /// 13 | /// Returns true if session_id has the right form (66 hex digits). This is a quick check, not a 14 | /// robust one: it does not check the leading byte prefix, nor the cryptographic properties of the 15 | /// pubkey for actual validity. 16 | /// 17 | /// Declaration: 18 | /// ```cpp 19 | /// BOOL session_id_is_valid( 20 | /// [in] const char* session_id 21 | /// ); 22 | /// ``` 23 | /// 24 | /// Inputs: 25 | /// - `session_id` -- [in] hex string of the session id 26 | /// 27 | /// Outputs: 28 | /// - `bool` -- Returns true if the session id has the right form 29 | LIBSESSION_EXPORT bool session_id_is_valid(const char* session_id); 30 | 31 | #ifdef __cplusplus 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /include/session/curve25519.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | #include "export.h" 10 | 11 | /// API: crypto/session_curve25519_key_pair 12 | /// 13 | /// Generates a random curve25519 key pair. 14 | /// 15 | /// Inputs: 16 | /// - `curve25519_pk_out` -- [out] pointer to a buffer of 32 bytes where the public key will be 17 | /// written. 18 | /// - `curve25519_sk_out` -- [out] pointer to a buffer of 32 bytes where the private key will be 19 | /// written. 20 | /// 21 | /// Outputs: 22 | /// - `bool` -- True if the seed was successfully retrieved, false if failed. 23 | LIBSESSION_EXPORT bool session_curve25519_key_pair( 24 | unsigned char* curve25519_pk_out, /* 32 byte output buffer */ 25 | unsigned char* curve25519_sk_out /* 32 byte output buffer */); 26 | 27 | /// API: crypto/session_to_curve25519_pubkey 28 | /// 29 | /// Generates a curve25519 public key for an ed25519 public key. 30 | /// 31 | /// Inputs: 32 | /// - `ed25519_pubkey` -- the ed25519 public key (32 bytes). 33 | /// - `curve25519_pk_out` -- [out] pointer to a buffer of 32 bytes where the public key will be 34 | /// written. 35 | /// 36 | /// Outputs: 37 | /// - `bool` -- True if the public key was successfully generated, false if failed. 38 | LIBSESSION_EXPORT bool session_to_curve25519_pubkey( 39 | const unsigned char* ed25519_pubkey, /* 32 bytes */ 40 | unsigned char* curve25519_pk_out /* 32 byte output buffer */); 41 | 42 | /// API: crypto/session_to_curve25519_seckey 43 | /// 44 | /// Generates a curve25519 secret key given given either a libsodium-style secret key, 64 45 | /// bytes. Can also be passed as a 32-byte seed. 46 | /// 47 | /// Inputs: 48 | /// - `ed25519_seckey` -- [in] the libsodium-style secret key, 64 bytes. Can also be 49 | /// passed as a 32-byte seed. 50 | /// - `curve25519_sk_out` -- [out] pointer to a buffer of 32 bytes where the secret key will be 51 | /// written. 52 | /// 53 | /// Outputs: 54 | /// - `bool` -- True if the secret key was successfully generated, false if failed. 55 | LIBSESSION_EXPORT bool session_to_curve25519_seckey( 56 | const unsigned char* ed25519_seckey, /* 64 bytes */ 57 | unsigned char* curve25519_sk_out /* 32 byte output buffer */); 58 | 59 | #ifdef __cplusplus 60 | } 61 | #endif 62 | -------------------------------------------------------------------------------- /include/session/curve25519.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "types.hpp" 6 | 7 | namespace session::curve25519 { 8 | 9 | /// Generates a random curve25519 key pair 10 | std::pair, std::array> curve25519_key_pair(); 11 | 12 | /// API: curve25519/to_curve25519_pubkey 13 | /// 14 | /// Generates a curve25519 public key for an ed25519 public key. 15 | /// 16 | /// Inputs: 17 | /// - `ed25519_pubkey` -- the ed25519 public key. 18 | /// 19 | /// Outputs: 20 | /// - The curve25519 public key 21 | std::array to_curve25519_pubkey(ustring_view ed25519_pubkey); 22 | 23 | /// API: curve25519/to_curve25519_seckey 24 | /// 25 | /// Generates a curve25519 secret key given given a libsodium-style secret key, 64 26 | /// bytes. 27 | /// 28 | /// Inputs: 29 | /// - `ed25519_seckey` -- the libsodium-style secret key, 64 bytes. 30 | /// 31 | /// Outputs: 32 | /// - The curve25519 secret key 33 | std::array to_curve25519_seckey(ustring_view ed25519_seckey); 34 | 35 | } // namespace session::curve25519 36 | -------------------------------------------------------------------------------- /include/session/ed25519.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | #include "export.h" 10 | 11 | /// API: crypto/session_ed25519_key_pair 12 | /// 13 | /// Generates a random ed25519 key pair. 14 | /// 15 | /// Inputs: 16 | /// - `ed25519_pk_out` -- [out] pointer to a buffer of 32 bytes where the public key will be 17 | /// written. 18 | /// - `ed25519_sk_out` -- [out] pointer to a buffer of 64 bytes where the private key will be 19 | /// written. 20 | /// 21 | /// Outputs: 22 | /// - `bool` -- True if the seed was successfully retrieved, false if failed. 23 | LIBSESSION_EXPORT bool session_ed25519_key_pair( 24 | unsigned char* ed25519_pk_out, /* 32 byte output buffer */ 25 | unsigned char* ed25519_sk_out /* 64 byte output buffer */); 26 | 27 | /// API: crypto/session_ed25519_key_pair_seed 28 | /// 29 | /// Generates a ed25519 key pair for a 32 byte seed. 30 | /// 31 | /// Inputs: 32 | /// - `ed25519_seed` -- [in] the 32 byte seed. 33 | /// - `ed25519_pk_out` -- [out] pointer to a buffer of 32 bytes where the public key will be 34 | /// written. 35 | /// - `ed25519_sk_out` -- [out] pointer to a buffer of 64 bytes where the private key will be 36 | /// written. 37 | /// 38 | /// Outputs: 39 | /// - `bool` -- True if the seed was successfully retrieved, false if failed. 40 | LIBSESSION_EXPORT bool session_ed25519_key_pair_seed( 41 | const unsigned char* ed25519_seed, /* 32 bytes */ 42 | unsigned char* ed25519_pk_out, /* 32 byte output buffer */ 43 | unsigned char* ed25519_sk_out /* 64 byte output buffer */); 44 | 45 | /// API: crypto/session_seed_for_ed_privkey 46 | /// 47 | /// Returns the seed for an ed25519 key pair given either the libsodium-style secret key, 64 48 | /// bytes. 49 | /// 50 | /// Inputs: 51 | /// - `ed25519_privkey` -- [in] the libsodium-style secret key of the sender, 64 bytes. 52 | /// - `ed25519_seed_out` -- [out] pointer to a buffer of 32 bytes where the seed will be written. 53 | /// 54 | /// Outputs: 55 | /// - `bool` -- True if the seed was successfully retrieved, false if failed. 56 | LIBSESSION_EXPORT bool session_seed_for_ed_privkey( 57 | const unsigned char* ed25519_privkey, /* 64 bytes */ 58 | unsigned char* ed25519_seed_out /* 32 byte output buffer */); 59 | 60 | /// API: crypto/session_ed25519_sign 61 | /// 62 | /// Generates a signature for the message using the libsodium-style ed25519 secret key, 64 bytes. 63 | /// 64 | /// Inputs: 65 | /// - `ed25519_privkey` -- [in] the libsodium-style secret key of the sender, 64 bytes. 66 | /// - `msg` -- [in] the data to generate a signature for. 67 | /// - `msg_len` -- [in] the length of the `msg` data. 68 | /// - `ed25519_sig_out` -- [out] pointer to a buffer of 64 bytes where the signature will be 69 | /// written. 70 | /// 71 | /// Outputs: 72 | /// - `bool` -- True if the seed was successfully retrieved, false if failed. 73 | LIBSESSION_EXPORT bool session_ed25519_sign( 74 | const unsigned char* ed25519_privkey, /* 64 bytes */ 75 | const unsigned char* msg, 76 | size_t msg_len, 77 | unsigned char* ed25519_sig_out /* 64 byte output buffer */); 78 | 79 | /// API: crypto/session_ed25519_verify 80 | /// 81 | /// Verify a message and signature for a given pubkey. 82 | /// 83 | /// Inputs: 84 | /// - `sig` -- [in] the signature to verify, 64 bytes. 85 | /// - `pubkey` -- [in] the pubkey for the secret key that was used to generate the signature, 32 86 | /// bytes. 87 | /// - `msg` -- [in] the data to verify the signature for. 88 | /// - `msg_len` -- [in] the length of the `msg` data. 89 | /// 90 | /// Outputs: 91 | /// - A flag indicating whether the signature is valid 92 | LIBSESSION_EXPORT bool session_ed25519_verify( 93 | const unsigned char* sig, /* 64 bytes */ 94 | const unsigned char* pubkey, 95 | const unsigned char* msg, 96 | size_t msg_len); 97 | 98 | #ifdef __cplusplus 99 | } 100 | #endif 101 | -------------------------------------------------------------------------------- /include/session/ed25519.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "types.hpp" 6 | 7 | namespace session::ed25519 { 8 | 9 | /// Generates a random Ed25519 key pair 10 | std::pair, std::array> ed25519_key_pair(); 11 | 12 | /// Given an Ed25519 seed this returns the associated Ed25519 key pair 13 | std::pair, std::array> ed25519_key_pair( 14 | ustring_view ed25519_seed); 15 | 16 | /// API: ed25519/seed_for_ed_privkey 17 | /// 18 | /// Returns the seed for an ed25519 key pair given either the libsodium-style secret key, 64 19 | /// bytes. If a 32-byte value is provided it is assumed to be the seed and the value will just 20 | /// be returned directly. 21 | /// 22 | /// Inputs: 23 | /// - `ed25519_privkey` -- the libsodium-style secret key of the sender, 64 bytes. Can also be 24 | /// passed as a 32-byte seed. 25 | /// 26 | /// Outputs: 27 | /// - The ed25519 seed 28 | std::array seed_for_ed_privkey(ustring_view ed25519_privkey); 29 | 30 | /// API: ed25519/sign 31 | /// 32 | /// Generates a signature for the message using the libsodium-style ed25519 secret key, 64 bytes. 33 | /// 34 | /// Inputs: 35 | /// - `ed25519_privkey` -- the libsodium-style secret key, 64 bytes. 36 | /// - `msg` -- the data to generate a signature for. 37 | /// 38 | /// Outputs: 39 | /// - The ed25519 signature 40 | ustring sign(ustring_view ed25519_privkey, ustring_view msg); 41 | 42 | /// API: ed25519/verify 43 | /// 44 | /// Verify a message and signature for a given pubkey. 45 | /// 46 | /// Inputs: 47 | /// - `sig` -- the signature to verify, 64 bytes. 48 | /// - `pubkey` -- the pubkey for the secret key that was used to generate the signature, 32 bytes. 49 | /// - `msg` -- the data to verify the signature for. 50 | /// 51 | /// Outputs: 52 | /// - A flag indicating whether the signature is valid 53 | bool verify(ustring_view sig, ustring_view pubkey, ustring_view msg); 54 | 55 | } // namespace session::ed25519 56 | -------------------------------------------------------------------------------- /include/session/export.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(_WIN32) || defined(WIN32) 4 | #define LIBSESSION_EXPORT __declspec(dllexport) 5 | #else 6 | #define LIBSESSION_EXPORT __attribute__((visibility("default"))) 7 | #endif 8 | #define LIBSESSION_C_API extern "C" LIBSESSION_EXPORT 9 | 10 | #ifdef __GNUC__ 11 | #define LIBSESSION_WARN_UNUSED __attribute__((warn_unused_result)) 12 | #else 13 | #define LIBSESSION_WARN_UNUSED 14 | #endif -------------------------------------------------------------------------------- /include/session/fields.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace session { 9 | 10 | using namespace std::literals; 11 | 12 | /// An uploaded file is its URL + decryption key 13 | struct Uploaded { 14 | std::string url; 15 | std::string key; 16 | }; 17 | 18 | /// A conversation disappearing messages setting 19 | struct Disappearing { 20 | /// The possible modes of a disappearing messages setting. 21 | enum class Mode : int { None = 0, AfterSend = 1, AfterRead = 2 }; 22 | 23 | /// The mode itself 24 | Mode mode = Mode::None; 25 | 26 | /// The timer value; this is only used when mode is not None. 27 | std::chrono::seconds timer = 0s; 28 | }; 29 | 30 | /// A Session ID: an x25519 pubkey, with a 05 identifying prefix. On the wire we send just the 31 | /// 32-byte pubkey value (i.e. not hex, without the prefix). 32 | struct SessionID { 33 | /// The fixed session netid, 0x05 34 | static constexpr unsigned char netid = 0x05; 35 | 36 | /// The raw x25519 pubkey, as bytes 37 | std::array pubkey; 38 | 39 | /// Returns the full pubkey in hex, including the netid prefix. 40 | std::string hex() const; 41 | }; 42 | 43 | } // namespace session 44 | -------------------------------------------------------------------------------- /include/session/hash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | #include "export.h" 10 | 11 | /// API: crypto/session_hash 12 | /// 13 | /// Wrapper around the crypto_generichash_blake2b function. 14 | /// 15 | /// Inputs: 16 | /// - `size` -- [in] length of the hash to be generated. 17 | /// - `msg_in` -- [in] the message a hash should be generated for. 18 | /// - `msg_len` -- [in] length of `msg_in`. 19 | /// - `key_in` -- [in] an optional key to be used when generating the hash. 20 | /// - `key_len` -- [in] length of `key_in`. 21 | /// - `hash_out` -- [out] pointer to a buffer of at least `size` bytes where the 22 | /// hash will be written. 23 | /// 24 | /// Outputs: 25 | /// - `bool` -- True if the generation was successful, false if generation failed. 26 | LIBSESSION_EXPORT bool session_hash( 27 | size_t size, 28 | const unsigned char* msg_in, 29 | size_t msg_len, 30 | const unsigned char* key_in, 31 | size_t key_len, 32 | unsigned char* hash_out); 33 | 34 | #ifdef __cplusplus 35 | } 36 | #endif 37 | -------------------------------------------------------------------------------- /include/session/hash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "types.hpp" 6 | 7 | namespace session::hash { 8 | 9 | /// API: hash/hash 10 | /// 11 | /// Wrapper around the crypto_generichash_blake2b function. 12 | /// 13 | /// Inputs: 14 | /// - `size` -- length of the hash to be generated. 15 | /// - `msg` -- the message to generate a hash for. 16 | /// - `key` -- an optional key to be used when generating the hash. Can be omitted or an empty 17 | /// string for an unkeyed hash. 18 | /// 19 | /// Outputs: 20 | /// - a `size` byte hash. 21 | ustring hash(const size_t size, ustring_view msg, std::optional key = std::nullopt); 22 | 23 | } // namespace session::hash 24 | -------------------------------------------------------------------------------- /include/session/onionreq/builder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | #include "../export.h" 11 | 12 | typedef enum ENCRYPT_TYPE { 13 | ENCRYPT_TYPE_AES_GCM = 0, 14 | ENCRYPT_TYPE_X_CHA_CHA_20 = 1, 15 | } ENCRYPT_TYPE; 16 | 17 | typedef struct onion_request_builder_object { 18 | // Internal opaque object pointer; calling code should leave this alone. 19 | void* internals; 20 | 21 | ENCRYPT_TYPE enc_type; 22 | } onion_request_builder_object; 23 | 24 | /// API: groups/onion_request_builder_init 25 | /// 26 | /// Constructs an onion request builder and sets a pointer to it in `builder`. 27 | /// 28 | /// When done with the object the `builder` must be destroyed by passing the pointer to 29 | /// onion_request_builder_free(). 30 | /// 31 | /// Inputs: 32 | /// - `builder` -- [out] Pointer to the builder object 33 | LIBSESSION_EXPORT void onion_request_builder_init(onion_request_builder_object** builder); 34 | 35 | /// API: groups/onion_request_builder_free 36 | /// 37 | /// Properly destroys an onion request builder instance. 38 | /// 39 | /// Inputs: 40 | /// - `builder` -- [out] Pointer to the builder object to be freed 41 | LIBSESSION_EXPORT void onion_request_builder_free(onion_request_builder_object* builder); 42 | 43 | /// API: onion_request_builder_set_enc_type 44 | /// 45 | /// Wrapper around session::onionreq::Builder::onion_request_builder_set_enc_type. 46 | /// 47 | /// Declaration: 48 | /// ```cpp 49 | /// void onion_request_builder_set_enc_type( 50 | /// [in] onion_request_builder_object* builder 51 | /// [in] ENCRYPT_TYPE enc_type 52 | /// ); 53 | /// ``` 54 | /// 55 | /// Inputs: 56 | /// - `builder` -- [in] Pointer to the builder object 57 | /// - `enc_type` -- [in] The encryption type to use in the onion request 58 | LIBSESSION_EXPORT void onion_request_builder_set_enc_type( 59 | onion_request_builder_object* builder, ENCRYPT_TYPE enc_type); 60 | 61 | /// API: onion_request_builder_set_snode_destination 62 | /// 63 | /// Wrapper around session::onionreq::Builder::set_snode_destination. ed25519_pubkey and 64 | /// x25519_pubkey are both hex strings and must both be exactly 64 characters. 65 | /// 66 | /// Declaration: 67 | /// ```cpp 68 | /// void onion_request_builder_set_snode_destination( 69 | /// [in] onion_request_builder_object* builder 70 | /// [in] const char* ed25519_pubkey, 71 | /// [in] const char* x25519_pubkey 72 | /// ); 73 | /// ``` 74 | /// 75 | /// Inputs: 76 | /// - `builder` -- [in] Pointer to the builder object 77 | /// - `ed25519_pubkey` -- [in] The ed25519 public key for the snode destination 78 | /// - `x25519_pubkey` -- [in] The x25519 public key for the snode destination 79 | LIBSESSION_EXPORT void onion_request_builder_set_snode_destination( 80 | onion_request_builder_object* builder, 81 | const char* ed25519_pubkey, 82 | const char* x25519_pubkey); 83 | 84 | /// API: onion_request_builder_set_server_destination 85 | /// 86 | /// Wrapper around session::onionreq::Builder::set_server_destination. x25519_pubkey 87 | /// is a hex string and must both be exactly 64 characters. 88 | /// 89 | /// Declaration: 90 | /// ```cpp 91 | /// void onion_request_builder_set_server_destination( 92 | /// [in] onion_request_builder_object* builder 93 | /// [in] const char* host, 94 | /// [in] const char* target, 95 | /// [in] const char* protocol, 96 | /// [in] uint16_t port, 97 | /// [in] const char* x25519_pubkey 98 | /// ); 99 | /// ``` 100 | /// 101 | /// Inputs: 102 | /// - `builder` -- [in] Pointer to the builder object 103 | /// - `host` -- [in] The host for the server destination 104 | /// - `target` -- [in] The target (endpoint) for the server destination 105 | /// - `protocol` -- [in] The protocol to use for the 106 | /// - `port` -- [in] The host for the server destination 107 | /// - `x25519_pubkey` -- [in] The x25519 public key for the snode destination 108 | LIBSESSION_EXPORT void onion_request_builder_set_server_destination( 109 | onion_request_builder_object* builder, 110 | const char* host, 111 | const char* target, 112 | const char* protocol, 113 | uint16_t port, 114 | const char* x25519_pubkey); 115 | 116 | /// API: onion_request_builder_add_hop 117 | /// 118 | /// Wrapper around session::onionreq::Builder::add_hop. ed25519_pubkey and 119 | /// x25519_pubkey are both hex strings and must both be exactly 64 characters. 120 | /// 121 | /// Declaration: 122 | /// ```cpp 123 | /// void onion_request_builder_add_hop( 124 | /// [in] onion_request_builder_object* builder 125 | /// [in] const char* ed25519_pubkey, 126 | /// [in] const char* x25519_pubkey 127 | /// ); 128 | /// ``` 129 | /// 130 | /// Inputs: 131 | /// - `builder` -- [in] Pointer to the builder object 132 | /// - `ed25519_pubkey` -- [in] The ed25519 public key for the snode hop 133 | /// - `x25519_pubkey` -- [in] The x25519 public key for the snode hop 134 | LIBSESSION_EXPORT void onion_request_builder_add_hop( 135 | onion_request_builder_object* builder, 136 | const char* ed25519_pubkey, 137 | const char* x25519_pubkey); 138 | 139 | /// API: onion_request_builder_build 140 | /// 141 | /// Wrapper around session::onionreq::Builder::build. payload_in is binary: payload_in 142 | /// has the length provided, destination_ed25519_pubkey and destination_x25519_pubkey 143 | /// are both hex strings and must both be exactly 64 characters. Returns a flag indicating 144 | /// success or failure. 145 | /// 146 | /// Declaration: 147 | /// ```cpp 148 | /// bool onion_request_builder_build( 149 | /// [in] onion_request_builder_object* builder 150 | /// [in] const unsigned char* payload_in, 151 | /// [in] size_t payload_in_len, 152 | /// [out] unsigned char** payload_out, 153 | /// [out] size_t* payload_out_len, 154 | /// [out] unsigned char* final_x25519_pubkey_out, 155 | /// [out] unsigned char* final_x25519_seckey_out 156 | /// ); 157 | /// ``` 158 | /// 159 | /// Inputs: 160 | /// - `builder` -- [in] Pointer to the builder object 161 | /// - `payload_in` -- [in] The payload to be sent in the onion request 162 | /// - `payload_in_len` -- [in] The length of the payload_in 163 | /// - `payload_out` -- [out] payload to be sent through the network, will be nullptr on error 164 | /// - `payload_out_len` -- [out] length of payload_out if not null 165 | /// - `final_x25519_pubkey_out` -- [out] pointer to a buffer of exactly 32 bytes where the final 166 | /// x25519 public key used for the onion request will be written if successful 167 | /// - `final_x25519_seckey_out` -- [out] pointer to a buffer of exactly 32 bytes where the final 168 | /// x25519 secret key used for the onion request will be written if successful 169 | /// 170 | /// Outputs: 171 | /// - `bool` -- True if the onion request payload was successfully constructed, false if it failed. 172 | /// If (and only if) true is returned then `payload_out` must be freed when done with it. 173 | LIBSESSION_EXPORT bool onion_request_builder_build( 174 | onion_request_builder_object* builder, 175 | const unsigned char* payload_in, 176 | size_t payload_in_len, 177 | unsigned char** payload_out, 178 | size_t* payload_out_len, 179 | unsigned char* final_x25519_pubkey_out, 180 | unsigned char* final_x25519_seckey_out); 181 | 182 | #ifdef __cplusplus 183 | } 184 | #endif 185 | -------------------------------------------------------------------------------- /include/session/onionreq/builder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "key_types.hpp" 7 | 8 | namespace session::onionreq { 9 | 10 | enum class EncryptType { 11 | aes_gcm, 12 | xchacha20, 13 | }; 14 | 15 | // Takes the encryption type as a string, returns the EncryptType value (or throws if invalid). 16 | // Supported values: aes-gcm and xchacha20. gcm is accepted as an aliases for aes-gcm. 17 | EncryptType parse_enc_type(std::string_view enc_type); 18 | 19 | inline constexpr std::string_view to_string(EncryptType type) { 20 | switch (type) { 21 | case EncryptType::xchacha20: return "xchacha20"sv; 22 | case EncryptType::aes_gcm: return "aes-gcm"sv; 23 | } 24 | return ""sv; 25 | } 26 | 27 | // Builder class for preparing onion request payloads. 28 | class Builder { 29 | public: 30 | EncryptType enc_type; 31 | std::optional destination_x25519_public_key = std::nullopt; 32 | std::optional final_hop_x25519_keypair = std::nullopt; 33 | 34 | Builder(EncryptType enc_type_ = EncryptType::xchacha20) : enc_type{enc_type_} {} 35 | 36 | void set_enc_type(EncryptType enc_type_) { enc_type = enc_type_; } 37 | 38 | void set_snode_destination(ed25519_pubkey ed25519_public_key, x25519_pubkey x25519_public_key) { 39 | destination_x25519_public_key.reset(); 40 | ed25519_public_key_.reset(); 41 | destination_x25519_public_key.emplace(x25519_public_key); 42 | ed25519_public_key_.emplace(ed25519_public_key); 43 | } 44 | 45 | void set_server_destination( 46 | std::string host, 47 | std::string target, 48 | std::string protocol, 49 | std::optional port, 50 | x25519_pubkey x25519_public_key) { 51 | destination_x25519_public_key.reset(); 52 | 53 | host_.emplace(host); 54 | target_.emplace(target); 55 | protocol_.emplace(protocol); 56 | 57 | if (port) 58 | port_.emplace(*port); 59 | 60 | destination_x25519_public_key.emplace(x25519_public_key); 61 | } 62 | 63 | void add_hop(std::pair keys) { hops_.push_back(keys); } 64 | 65 | ustring build(ustring payload); 66 | 67 | private: 68 | std::vector> hops_ = {}; 69 | 70 | // Snode request values 71 | 72 | std::optional ed25519_public_key_ = std::nullopt; 73 | 74 | // Proxied request values 75 | 76 | std::optional host_ = std::nullopt; 77 | std::optional target_ = std::nullopt; 78 | std::optional protocol_ = std::nullopt; 79 | std::optional port_ = std::nullopt; 80 | }; 81 | 82 | } // namespace session::onionreq 83 | -------------------------------------------------------------------------------- /include/session/onionreq/hop_encryption.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "builder.hpp" 7 | #include "key_types.hpp" 8 | 9 | namespace session::onionreq { 10 | 11 | // Encryption/decription class for encryption/decrypting outgoing/incoming messages. 12 | class HopEncryption { 13 | public: 14 | HopEncryption(x25519_seckey private_key, x25519_pubkey public_key, bool server = true) : 15 | private_key_{std::move(private_key)}, 16 | public_key_{std::move(public_key)}, 17 | server_{server} {} 18 | 19 | // Encrypts `plaintext` message using encryption `type`. `pubkey` is the recipients public key. 20 | // `reply` should be false for a client-to-snode message, and true on a returning 21 | // snode-to-client message. 22 | ustring encrypt(EncryptType type, ustring plaintext, const x25519_pubkey& pubkey) const; 23 | ustring decrypt(EncryptType type, ustring ciphertext, const x25519_pubkey& pubkey) const; 24 | 25 | // AES-GCM encryption. 26 | ustring encrypt_aesgcm(ustring plainText, const x25519_pubkey& pubKey) const; 27 | ustring decrypt_aesgcm(ustring cipherText, const x25519_pubkey& pubKey) const; 28 | 29 | // xchacha20-poly1305 encryption; for a message sent from client Alice to server Bob we use a 30 | // shared key of a Blake2B 32-byte (i.e. crypto_aead_xchacha20poly1305_ietf_KEYBYTES) hash of 31 | // H(aB || A || B), which Bob can compute when receiving as H(bA || A || B). The returned value 32 | // always has the crypto_aead_xchacha20poly1305_ietf_NPUBBYTES nonce prepended to the beginning. 33 | // 34 | // When Bob (the server) encrypts a method for Alice (the client), he uses shared key 35 | // H(bA || A || B) (note that this is *different* that what would result if Bob was a client 36 | // sending to Alice the client). 37 | ustring encrypt_xchacha20(ustring plaintext, const x25519_pubkey& pubKey) const; 38 | ustring decrypt_xchacha20(ustring ciphertext, const x25519_pubkey& pubKey) const; 39 | 40 | private: 41 | const x25519_seckey private_key_; 42 | const x25519_pubkey public_key_; 43 | bool server_; // True if we are the server (i.e. the snode). 44 | }; 45 | 46 | } // namespace session::onionreq 47 | -------------------------------------------------------------------------------- /include/session/onionreq/key_types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../types.hpp" 14 | #include "../util.hpp" 15 | 16 | namespace session::onionreq { 17 | 18 | using namespace std::literals; 19 | 20 | namespace detail { 21 | template 22 | inline constexpr std::array null_bytes = {0}; 23 | 24 | void load_from_hex(void* buffer, size_t length, std::string_view hex); 25 | void load_from_bytes(void* buffer, size_t length, std::string_view bytes); 26 | 27 | } // namespace detail 28 | 29 | template 30 | struct alignas(size_t) key_base : std::array { 31 | std::string_view view() const { 32 | return {reinterpret_cast(this->data()), KeyLength}; 33 | } 34 | std::string hex() const { return oxenc::to_hex(view()); } 35 | explicit operator bool() const { return *this != detail::null_bytes; } 36 | 37 | // Loads the key from a hex string; throws if the hex is the wrong size or not hex. 38 | static Derived from_hex(std::string_view hex) { 39 | Derived d; 40 | detail::load_from_hex(d.data(), d.size(), hex); 41 | return d; 42 | } 43 | // Same as above, but returns nullopt if invalid instead of throwing 44 | static std::optional maybe_from_hex(std::string_view hex) { 45 | try { 46 | return from_hex(hex); 47 | } catch (...) { 48 | } 49 | return std::nullopt; 50 | } 51 | // Loads the key from a byte string; throws if the wrong size. 52 | static Derived from_bytes(std::string_view bytes) { 53 | Derived d; 54 | detail::load_from_bytes(d.data(), d.size(), bytes); 55 | return d; 56 | } 57 | static Derived from_bytes(ustring_view bytes) { return from_bytes(from_unsigned_sv(bytes)); } 58 | }; 59 | 60 | template 61 | struct pubkey_base : key_base { 62 | using PubKeyBase = pubkey_base; 63 | }; 64 | 65 | struct legacy_pubkey : pubkey_base {}; 66 | struct x25519_pubkey : pubkey_base {}; 67 | struct ed25519_pubkey : pubkey_base { 68 | // Returns the {base32z}.snode representation of this pubkey 69 | std::string snode_address() const; 70 | }; 71 | 72 | template 73 | struct seckey_base : key_base {}; 74 | 75 | struct legacy_seckey : seckey_base { 76 | legacy_pubkey pubkey() const; 77 | }; 78 | struct ed25519_seckey : seckey_base { 79 | ed25519_pubkey pubkey() const; 80 | }; 81 | struct x25519_seckey : seckey_base { 82 | x25519_pubkey pubkey() const; 83 | }; 84 | 85 | using legacy_keypair = std::pair; 86 | using ed25519_keypair = std::pair; 87 | using x25519_keypair = std::pair; 88 | 89 | /// Parse a pubkey string value encoded in any of base32z, b64, hex, or raw bytes, based on the 90 | /// length of the value. Returns a null pk (i.e. operator bool() returns false) and warns on 91 | /// invalid input (i.e. wrong length or invalid encoding). 92 | legacy_pubkey parse_legacy_pubkey(std::string_view pubkey_in); 93 | ed25519_pubkey parse_ed25519_pubkey(std::string_view pubkey_in); 94 | x25519_pubkey parse_x25519_pubkey(std::string_view pubkey_in); 95 | 96 | } // namespace session::onionreq 97 | 98 | namespace std { 99 | template 100 | struct hash> { 101 | size_t operator()(const session::onionreq::pubkey_base& pk) const { 102 | // pubkeys are already random enough to use the first bytes directly as a good (and fast) 103 | // hash value 104 | static_assert(alignof(decltype(pk)) >= alignof(size_t)); 105 | return *reinterpret_cast(pk.data()); 106 | } 107 | }; 108 | 109 | template <> 110 | struct hash : hash { 111 | }; 112 | template <> 113 | struct hash : hash { 114 | }; 115 | template <> 116 | struct hash 117 | : hash {}; 118 | 119 | } // namespace std 120 | -------------------------------------------------------------------------------- /include/session/onionreq/parser.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "session/onionreq/hop_encryption.hpp" 4 | #include "session/types.hpp" 5 | 6 | namespace session::onionreq { 7 | 8 | /// The default maximum size of an onion request accepted by the OnionReqParser constructor. 9 | constexpr size_t DEFAULT_MAX_SIZE = 10'485'760; // 10 MiB 10 | 11 | class OnionReqParser { 12 | private: 13 | x25519_keypair keys; 14 | HopEncryption enc; 15 | EncryptType enc_type = EncryptType::aes_gcm; 16 | x25519_pubkey remote_pk; 17 | ustring payload_; 18 | 19 | public: 20 | /// Constructs a parser, parsing the given request sent to us. Throws if parsing or decryption 21 | /// fails. 22 | OnionReqParser( 23 | ustring_view x25519_pubkey, 24 | ustring_view x25519_privkey, 25 | ustring_view req, 26 | size_t max_size = DEFAULT_MAX_SIZE); 27 | 28 | /// plaintext payload, decrypted from the incoming request during construction. 29 | ustring_view payload() const { return payload_; } 30 | 31 | /// Extracts payload from this object (via a std::move); after the call the object's payload 32 | /// will be empty. 33 | ustring move_payload() { 34 | ustring ret{std::move(payload_)}; 35 | payload_.clear(); // Guarantee empty, even if SSO active 36 | return ret; 37 | } 38 | 39 | ustring_view remote_pubkey() const { return to_unsigned_sv(remote_pk.view()); } 40 | 41 | /// Encrypts a reply using the appropriate encryption as determined when parsing the 42 | /// request. 43 | ustring encrypt_reply(ustring_view reply) const; 44 | }; 45 | 46 | } // namespace session::onionreq 47 | -------------------------------------------------------------------------------- /include/session/onionreq/response_parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | #include "../export.h" 11 | #include "builder.h" 12 | 13 | /// API: onion_request_decrypt 14 | /// 15 | /// Wrapper around session::onionreq::ResponseParser. ciphertext_in is binary. 16 | /// enc_type should be set to ENCRYPT_TYPE::ENCRYPT_TYPE_X_CHA_CHA_20 if it's not 17 | /// set when creating the builder destination_x25519_pubkey, final_x25519_pubkey 18 | /// and final_x25519_seckey should be in bytes and be exactly 32 bytes. Returns a 19 | /// flag indicating success or failure. 20 | /// 21 | /// Declaration: 22 | /// ```cpp 23 | /// bool onion_request_decrypt( 24 | /// [in] const unsigned char* ciphertext, 25 | /// [in] size_t ciphertext_len, 26 | /// [in] ENCRYPT_TYPE enc_type_, 27 | /// [in] const char* destination_x25519_pubkey, 28 | /// [in] const char* final_x25519_pubkey, 29 | /// [in] const char* final_x25519_seckey, 30 | /// [out] unsigned char** plaintext_out, 31 | /// [out] size_t* plaintext_out_len 32 | /// ); 33 | /// ``` 34 | /// 35 | /// Inputs: 36 | /// - `ciphertext` -- [in] The onion request response data 37 | /// - `ciphertext_len` -- [in] The length of ciphertext 38 | /// - `enc_type_` -- [in] The encryption type which was used for the onion request 39 | /// - `destination_x25519_pubkey` -- [in] The x25519 public key for the server destination 40 | /// - `final_x25519_pubkey` -- [in] The final x25519 public key used for the onion request 41 | /// - `final_x25519_seckey` -- [in] The final x25519 secret key used for the onion request 42 | /// - `plaintext_out` -- [out] decrypted content contained within ciphertext, will be nullptr on 43 | /// error 44 | /// - `plaintext_out_len` -- [out] length of plaintext_out if not null 45 | /// 46 | /// Outputs: 47 | /// - `bool` -- True if the onion request was successfully constructed, false if it failed. 48 | /// If (and only if) true is returned then `plaintext_out` must be freed when done with it. 49 | LIBSESSION_EXPORT bool onion_request_decrypt( 50 | const unsigned char* ciphertext, 51 | size_t ciphertext_len, 52 | ENCRYPT_TYPE enc_type_, 53 | unsigned char* destination_x25519_pubkey, 54 | unsigned char* final_x25519_pubkey, 55 | unsigned char* final_x25519_seckey, 56 | unsigned char** plaintext_out, 57 | size_t* plaintext_out_len); 58 | 59 | #ifdef __cplusplus 60 | } 61 | #endif -------------------------------------------------------------------------------- /include/session/onionreq/response_parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "hop_encryption.hpp" 6 | #include "key_types.hpp" 7 | 8 | namespace session::onionreq { 9 | 10 | class ResponseParser { 11 | public: 12 | /// Constructs a parser, parsing the given request sent to us. Throws if parsing or decryption 13 | /// fails. 14 | ResponseParser(session::onionreq::Builder builder); 15 | ResponseParser( 16 | x25519_pubkey destination_x25519_public_key, 17 | x25519_keypair x25519_keypair, 18 | EncryptType enc_type = EncryptType::xchacha20) : 19 | destination_x25519_public_key_{std::move(destination_x25519_public_key)}, 20 | x25519_keypair_{std::move(x25519_keypair)}, 21 | enc_type_{enc_type} {} 22 | 23 | ustring decrypt(ustring ciphertext) const; 24 | 25 | private: 26 | x25519_pubkey destination_x25519_public_key_; 27 | x25519_keypair x25519_keypair_; 28 | EncryptType enc_type_; 29 | }; 30 | 31 | } // namespace session::onionreq 32 | -------------------------------------------------------------------------------- /include/session/platform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | typedef enum CLIENT_PLATFORM { 8 | CLIENT_PLATFORM_ANDROID = 0, 9 | CLIENT_PLATFORM_DESKTOP = 1, 10 | CLIENT_PLATFORM_IOS = 2, 11 | } CLIENT_PLATFORM; 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /include/session/platform.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace session { 4 | 5 | enum class Platform { 6 | android, 7 | desktop, 8 | ios, 9 | }; 10 | 11 | } // namespace session 12 | -------------------------------------------------------------------------------- /include/session/random.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | #include "export.h" 10 | 11 | /// API: crypto/session_random 12 | /// 13 | /// Wrapper around the randombytes_buf function. 14 | /// 15 | /// Inputs: 16 | /// - `size` -- [in] number of bytes to be generated. 17 | /// 18 | /// Outputs: 19 | /// - `unsigned char*` -- pointer to random bytes of `size` bytes. 20 | LIBSESSION_EXPORT unsigned char* session_random(size_t size); 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /include/session/random.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.hpp" 4 | 5 | namespace session::random { 6 | 7 | /// API: random/random 8 | /// 9 | /// Wrapper around the randombytes_buf function. 10 | /// 11 | /// Inputs: 12 | /// - `size` -- the number of random bytes to be generated. 13 | /// 14 | /// Outputs: 15 | /// - random bytes of the specified length. 16 | ustring random(size_t size); 17 | 18 | } // namespace session::random 19 | -------------------------------------------------------------------------------- /include/session/types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace session { 8 | 9 | using ustring = std::basic_string; 10 | using ustring_view = std::basic_string_view; 11 | 12 | namespace config { 13 | 14 | using seqno_t = std::int64_t; 15 | 16 | } // namespace config 17 | 18 | } // namespace session 19 | -------------------------------------------------------------------------------- /include/session/version.h: -------------------------------------------------------------------------------- 1 | #ifdef __cplusplus 2 | extern "C" { 3 | #endif 4 | 5 | #include 6 | 7 | /// libsession-util version triplet (major, minor, patch) 8 | extern const uint16_t LIBSESSION_UTIL_VERSION[3]; 9 | 10 | /// Printable full libsession-util name and version string, such as `libsession-util v0.1.2-release` 11 | /// for a tagged release or `libsession-util v0.1.2-7f144eb5` for an untagged build. 12 | extern const char* LIBSESSION_UTIL_VERSION_FULL; 13 | 14 | /// Just the version component as a string, e.g. `v0.1.2-release`. 15 | extern const char* LIBSESSION_UTIL_VERSION_STR; 16 | 17 | #ifdef __cplusplus 18 | } // extern "C" 19 | #endif 20 | -------------------------------------------------------------------------------- /include/session/xed25519.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | #include "export.h" 10 | 11 | /// XEd25519-signed a message given a curve25519 privkey and message. Writes the 64-byte signature 12 | /// to `sig` on success and returns 0. Returns non-zero on failure. 13 | LIBSESSION_EXPORT bool session_xed25519_sign( 14 | unsigned char* signature /* 64 byte buffer */, 15 | const unsigned char* curve25519_privkey /* 32 bytes */, 16 | const unsigned char* msg, 17 | size_t msg_len); 18 | 19 | /// Verifies an XEd25519-signed message given a 64-byte signature, 32-byte curve25519 pubkey, and 20 | /// message. Returns 0 if the signature verifies successfully, non-zero on failure. 21 | LIBSESSION_EXPORT bool session_xed25519_verify( 22 | const unsigned char* signature /* 64 bytes */, 23 | const unsigned char* pubkey /* 32-bytes */, 24 | const unsigned char* msg, 25 | size_t msg_len); 26 | 27 | /// Given a curve25519 pubkey, this writes the associated XEd25519-derived Ed25519 pubkey into 28 | /// ed25519_pubkey. Note, however, that there are *two* possible Ed25519 pubkeys that could result 29 | /// in a given curve25519 pubkey: this always returns the positive value. You can get the other 30 | /// possibility (the negative) by flipping the sign bit, i.e. `returned_pubkey[31] |= 0x80`. 31 | /// Returns 0 on success, non-0 on failure. 32 | LIBSESSION_EXPORT bool session_xed25519_pubkey( 33 | unsigned char* ed25519_pubkey /* 32-byte output buffer */, 34 | const unsigned char* curve25519_pubkey /* 32 bytes */); 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | -------------------------------------------------------------------------------- /include/session/xed25519.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace session::xed25519 { 7 | 8 | using ustring_view = std::basic_string_view; 9 | 10 | /// XEd25519-signs a message given the curve25519 privkey and message. 11 | std::array sign( 12 | ustring_view curve25519_privkey /* 32 bytes */, ustring_view msg); 13 | 14 | /// "Softer" version that takes and returns strings of regular chars 15 | std::string sign(std::string_view curve25519_privkey /* 32 bytes */, std::string_view msg); 16 | 17 | /// Verifies a curve25519 message allegedly signed by the given curve25519 pubkey 18 | [[nodiscard]] bool verify( 19 | ustring_view signature /* 64 bytes */, 20 | ustring_view curve25519_pubkey /* 32 bytes */, 21 | ustring_view msg); 22 | 23 | /// "Softer" version that takes strings of regular chars 24 | [[nodiscard]] bool verify( 25 | std::string_view signature /* 64 bytes */, 26 | std::string_view curve25519_pubkey /* 32 bytes */, 27 | std::string_view msg); 28 | 29 | /// Given a curve25519 pubkey, this returns the associated XEd25519-derived Ed25519 pubkey. Note, 30 | /// however, that there are *two* possible Ed25519 pubkeys that could result in a given curve25519 31 | /// pubkey: this always returns the positive value. You can get the other possibility (the 32 | /// negative) by setting the sign bit, i.e. `returned_pubkey[31] |= 0x80`. 33 | std::array pubkey(ustring_view curve25519_pubkey); 34 | 35 | /// "Softer" version that takes/returns strings of regular chars 36 | std::string pubkey(std::string_view curve25519_pubkey); 37 | 38 | /// Utility function that provides a constant-time `if (b) f = g;` implementation for byte arrays. 39 | template 40 | void constant_time_conditional_assign( 41 | std::array& f, const std::array& g, bool b) { 42 | std::array x; 43 | for (size_t i = 0; i < x.size(); i++) 44 | x[i] = f[i] ^ g[i]; 45 | unsigned char mask = (unsigned char)(-(signed char)b); 46 | for (size_t i = 0; i < x.size(); i++) 47 | x[i] &= mask; 48 | for (size_t i = 0; i < x.size(); i++) 49 | f[i] ^= x[i]; 50 | } 51 | 52 | } // namespace session::xed25519 53 | -------------------------------------------------------------------------------- /libsession-util.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=@CMAKE_INSTALL_FULL_LIBDIR@ 4 | includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ 5 | 6 | Name: libsession-util 7 | Description: Session utility libraries 8 | Version: @PROJECT_VERSION@ 9 | 10 | Libs: -L${libdir} @libsession_target_links@ 11 | Libs.private: -lprotobuf-lite -lnettle 12 | Requires: liboxenc 13 | Requires.private: nettle protobuf-lite 14 | Cflags: -I${includedir} 15 | -------------------------------------------------------------------------------- /proto/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | libsession_static_bundle(protobuf::libprotobuf-lite) 3 | 4 | 5 | add_library(protos 6 | SessionProtos.pb.cc 7 | WebSocketResources.pb.cc) 8 | target_include_directories(protos PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 9 | target_link_libraries(protos PUBLIC protobuf::libprotobuf-lite) 10 | set_target_properties( 11 | protos PROPERTIES 12 | OUTPUT_NAME session-protos 13 | SOVERSION ${LIBSESSION_LIBVERSION}) 14 | 15 | libsession_static_bundle(protos) 16 | 17 | add_library(libsession::protos ALIAS protos) 18 | export( 19 | TARGETS protos 20 | NAMESPACE libsession:: 21 | FILE libsessionTargets.cmake 22 | ) 23 | list(APPEND libsession_export_targets protos) 24 | set(libsession_export_targets "${libsession_export_targets}" PARENT_SCOPE) 25 | 26 | 27 | add_custom_target(regen-protobuf 28 | protoc --cpp_out=. SessionProtos.proto WebSocketResources.proto 29 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 30 | ) 31 | -------------------------------------------------------------------------------- /proto/WebSocketResources.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | // iOS - since we use a modern proto-compiler, we must specify 8 | // the legacy proto format. 9 | syntax = "proto2"; 10 | 11 | // iOS - package name determines class prefix 12 | package WebSocketProtos; 13 | 14 | option optimize_for = LITE_RUNTIME; 15 | 16 | option java_package = "org.whispersystems.signalservice.internal.websocket"; 17 | option java_outer_classname = "WebSocketProtos"; 18 | 19 | message WebSocketRequestMessage { 20 | // @required 21 | optional string verb = 1; 22 | // @required 23 | optional string path = 2; 24 | optional bytes body = 3; 25 | repeated string headers = 5; 26 | // @required 27 | optional uint64 requestId = 4; 28 | } 29 | 30 | message WebSocketResponseMessage { 31 | // @required 32 | optional uint64 requestId = 1; 33 | // @required 34 | optional uint32 status = 2; 35 | optional string message = 3; 36 | repeated string headers = 5; 37 | optional bytes body = 4; 38 | } 39 | 40 | message WebSocketMessage { 41 | enum Type { 42 | UNKNOWN = 0; 43 | REQUEST = 1; 44 | RESPONSE = 2; 45 | } 46 | 47 | // @required 48 | optional Type type = 1; 49 | optional WebSocketRequestMessage request = 2; 50 | optional WebSocketResponseMessage response = 3; 51 | } 52 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_library(common INTERFACE) 3 | target_link_libraries(common INTERFACE 4 | oxenc::oxenc) 5 | 6 | target_include_directories(common INTERFACE ../include) 7 | if(WARNINGS_AS_ERRORS) 8 | target_compile_options(common INTERFACE -Werror) 9 | message(STATUS "Compiling with fatal warnings (-Werror)") 10 | if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND 11 | CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 11 AND 12 | CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14) 13 | # -Wstringop-overflow triggers (falsely) in protobuf in GCC 11/12/13 so disable it there 14 | # (fingers crossed for 14). 15 | message(STATUS "Disabling -Werror for buggy GCC stringop-overflow") 16 | target_compile_options(common INTERFACE -Wno-error=stringop-overflow) 17 | endif() 18 | endif() 19 | 20 | 21 | set(export_targets) 22 | macro(add_libsession_util_library name) 23 | add_library(${name} ${ARGN}) 24 | 25 | set_target_properties( 26 | ${name} 27 | PROPERTIES 28 | OUTPUT_NAME session-${name} 29 | SOVERSION ${LIBSESSION_LIBVERSION}) 30 | 31 | libsession_static_bundle(${name}) 32 | 33 | list(APPEND export_targets ${name}) 34 | endmacro() 35 | 36 | 37 | if(NOT BUILD_STATIC_DEPS) 38 | find_package(PkgConfig REQUIRED) 39 | 40 | if(NOT TARGET nettle) 41 | pkg_check_modules(NETTLE nettle IMPORTED_TARGET REQUIRED) 42 | add_library(nettle INTERFACE IMPORTED) 43 | target_link_libraries(nettle INTERFACE PkgConfig::NETTLE) 44 | endif() 45 | endif() 46 | 47 | 48 | add_libsession_util_library(crypto 49 | blinding.cpp 50 | curve25519.cpp 51 | ed25519.cpp 52 | hash.cpp 53 | multi_encrypt.cpp 54 | random.cpp 55 | session_encrypt.cpp 56 | sodium_array.cpp 57 | util.cpp 58 | xed25519.cpp 59 | ) 60 | 61 | add_libsession_util_library(config 62 | bt_merge.cpp 63 | config.cpp 64 | config/base.cpp 65 | config/community.cpp 66 | config/contacts.cpp 67 | config/convo_info_volatile.cpp 68 | config/encrypt.cpp 69 | config/error.c 70 | config/groups/info.cpp 71 | config/groups/keys.cpp 72 | config/groups/members.cpp 73 | config/internal.cpp 74 | config/protos.cpp 75 | config/user_groups.cpp 76 | config/user_profile.cpp 77 | fields.cpp 78 | ) 79 | 80 | 81 | 82 | target_link_libraries(crypto 83 | PUBLIC 84 | common 85 | PRIVATE 86 | libsodium::sodium-internal 87 | ) 88 | 89 | target_link_libraries(config 90 | PUBLIC 91 | crypto 92 | common 93 | libsession::protos 94 | PRIVATE 95 | libsodium::sodium-internal 96 | libzstd::static 97 | ) 98 | 99 | if(ENABLE_ONIONREQ) 100 | add_libsession_util_library(onionreq 101 | onionreq/builder.cpp 102 | onionreq/hop_encryption.cpp 103 | onionreq/key_types.cpp 104 | onionreq/parser.cpp 105 | onionreq/response_parser.cpp 106 | ) 107 | 108 | target_link_libraries(onionreq 109 | PUBLIC 110 | crypto 111 | common 112 | PRIVATE 113 | nlohmann_json::nlohmann_json 114 | libsodium::sodium-internal 115 | nettle 116 | ) 117 | endif() 118 | 119 | 120 | if(WARNINGS_AS_ERRORS AND NOT USE_LTO AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION MATCHES "^11\\.") 121 | # GCC 11 has an overzealous (and false) stringop-overread warning, but only when LTO is off. 122 | # Um, yeah. 123 | target_compile_options(config PUBLIC -Wno-error=stringop-overread) 124 | endif() 125 | 126 | 127 | if(LIBSESSION_UTIL_VERSIONTAG) 128 | set(PROJECT_VERSION_TAG "${LIBSESSION_UTIL_VERSIONTAG}") 129 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.c.in" "${CMAKE_CURRENT_BINARY_DIR}/version.c") 130 | else() 131 | find_package(Git) 132 | if(EXISTS "${PROJECT_SOURCE_DIR}/.git/index" AND GIT_FOUND) 133 | add_custom_command( 134 | OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/version.c" 135 | COMMAND 136 | "${CMAKE_COMMAND}" 137 | "-DGIT=${GIT_EXECUTABLE}" 138 | "-DPROJECT_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}" 139 | "-DPROJECT_VERSION_MINOR=${PROJECT_VERSION_MINOR}" 140 | "-DPROJECT_VERSION_PATCH=${PROJECT_VERSION_PATCH}" 141 | "-DSRC=${CMAKE_CURRENT_SOURCE_DIR}/version.c.in" 142 | "-DDEST=${CMAKE_CURRENT_BINARY_DIR}/version.c" 143 | "-P" "${PROJECT_SOURCE_DIR}/cmake/GenVersion.cmake" 144 | DEPENDS 145 | "${CMAKE_CURRENT_SOURCE_DIR}/version.c.in" 146 | "${PROJECT_SOURCE_DIR}/.git/index") 147 | else() 148 | message(STATUS "Git was not found or this is not a git checkout. Setting version tag to 'unknown'") 149 | set(PROJECT_VERSION_TAG "nogit") 150 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.c.in" "${CMAKE_CURRENT_BINARY_DIR}/version.c") 151 | endif() 152 | endif() 153 | add_library(version STATIC version.c) 154 | libsession_static_bundle(version) 155 | target_include_directories(version PRIVATE ../include) 156 | target_link_libraries(common INTERFACE version) 157 | 158 | 159 | foreach(tgt ${export_targets}) 160 | add_library("libsession::${tgt}" ALIAS "${tgt}") 161 | endforeach() 162 | export( 163 | TARGETS ${export_targets} common version 164 | NAMESPACE libsession:: 165 | APPEND FILE libsessionTargets.cmake 166 | ) 167 | 168 | list(APPEND libsession_export_targets ${export_targets}) 169 | set(libsession_export_targets "${libsession_export_targets}" PARENT_SCOPE) 170 | -------------------------------------------------------------------------------- /src/bt_merge.cpp: -------------------------------------------------------------------------------- 1 | #include "session/bt_merge.hpp" 2 | 3 | namespace session::bt { 4 | bt_dict merge(const bt_dict& a, const bt_dict& b) { 5 | bt_dict result; 6 | auto it_a = a.begin(); 7 | auto it_b = b.begin(); 8 | while (it_a != a.end() || it_b != b.end()) { 9 | auto c = it_b == b.end() ? -1 : it_a == a.end() ? 1 : it_a->first.compare(it_b->first); 10 | if (c <= 0) { 11 | result.insert(result.end(), *it_a++); 12 | if (c == 0) 13 | ++it_b; // equal keys: ignore value from b 14 | } else { 15 | result.insert(result.end(), *it_b++); 16 | } 17 | } 18 | return result; 19 | } 20 | 21 | } // namespace session::bt 22 | -------------------------------------------------------------------------------- /src/config/encrypt.cpp: -------------------------------------------------------------------------------- 1 | #include "session/config/encrypt.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "session/export.h" 11 | 12 | using namespace std::literals; 13 | 14 | namespace session::config { 15 | 16 | namespace { 17 | 18 | // Helper function to go from char pointers to the unsigned char pointers sodium needs: 19 | const unsigned char* to_unsigned(const char* x) { 20 | return reinterpret_cast(x); 21 | } 22 | 23 | ustring_view to_unsigned_sv(std::string_view v) { 24 | return {to_unsigned(v.data()), v.size()}; 25 | } 26 | 27 | } // namespace 28 | 29 | static constexpr size_t DOMAIN_MAX_SIZE = 24; 30 | static constexpr auto NONCE_KEY_PREFIX = "libsessionutil-config-encrypted-"sv; 31 | static_assert(NONCE_KEY_PREFIX.size() + DOMAIN_MAX_SIZE < crypto_generichash_blake2b_KEYBYTES_MAX); 32 | 33 | static std::array make_encrypt_key( 34 | ustring_view key_base, uint64_t message_size, std::string_view domain) { 35 | if (key_base.size() != 32) 36 | throw std::invalid_argument{"encrypt called with key_base != 32 bytes"}; 37 | if (domain.size() < 1 || domain.size() > DOMAIN_MAX_SIZE) 38 | throw std::invalid_argument{"encrypt called with domain size not in [1, 24]"}; 39 | 40 | // We hash the key because we're using a deterministic nonce: the `key_base` value is expected 41 | // to be a long-term value for which nonce reuse (via hash collision) would be bad: by 42 | // incorporating the domain and message size we at least vary the key to further restrict the 43 | // nonce reuse concern so that you would not only have to hash collide but also have it happen 44 | // on messages of identical sizes and identical domain. 45 | std::array key{0}; 46 | crypto_generichash_blake2b_state state; 47 | crypto_generichash_blake2b_init(&state, nullptr, 0, key.size()); 48 | crypto_generichash_blake2b_update(&state, key_base.data(), key_base.size()); 49 | oxenc::host_to_big_inplace(message_size); 50 | crypto_generichash_blake2b_update( 51 | &state, reinterpret_cast(&message_size), sizeof(message_size)); 52 | crypto_generichash_blake2b_update(&state, to_unsigned(domain.data()), domain.size()); 53 | crypto_generichash_blake2b_final(&state, key.data(), key.size()); 54 | return key; 55 | } 56 | 57 | ustring encrypt(ustring_view message, ustring_view key_base, std::string_view domain) { 58 | ustring msg; 59 | msg.reserve( 60 | message.size() + crypto_aead_xchacha20poly1305_ietf_ABYTES + 61 | crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); 62 | msg.assign(message); 63 | encrypt_inplace(msg, key_base, domain); 64 | return msg; 65 | } 66 | void encrypt_inplace(ustring& message, ustring_view key_base, std::string_view domain) { 67 | auto key = make_encrypt_key(key_base, message.size(), domain); 68 | 69 | std::string nonce_key{NONCE_KEY_PREFIX}; 70 | nonce_key += domain; 71 | 72 | std::array nonce; 73 | crypto_generichash_blake2b( 74 | nonce.data(), 75 | nonce.size(), 76 | message.data(), 77 | message.size(), 78 | to_unsigned(nonce_key.data()), 79 | nonce_key.size()); 80 | 81 | size_t plaintext_len = message.size(); 82 | message.resize( 83 | plaintext_len + crypto_aead_xchacha20poly1305_ietf_ABYTES + 84 | crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); 85 | 86 | unsigned long long outlen = 0; 87 | crypto_aead_xchacha20poly1305_ietf_encrypt( 88 | message.data(), 89 | &outlen, 90 | message.data(), 91 | plaintext_len, 92 | nullptr, 93 | 0, 94 | nullptr, 95 | nonce.data(), 96 | key.data()); 97 | 98 | assert(outlen == message.size() - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); 99 | std::memcpy(message.data() + outlen, nonce.data(), nonce.size()); 100 | } 101 | 102 | static_assert( 103 | ENCRYPT_DATA_OVERHEAD == 104 | crypto_aead_xchacha20poly1305_IETF_ABYTES + crypto_aead_xchacha20poly1305_IETF_NPUBBYTES); 105 | 106 | ustring decrypt(ustring_view ciphertext, ustring_view key_base, std::string_view domain) { 107 | ustring x{ciphertext}; 108 | decrypt_inplace(x, key_base, domain); 109 | return x; 110 | } 111 | void decrypt_inplace(ustring& ciphertext, ustring_view key_base, std::string_view domain) { 112 | size_t message_len = ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES - 113 | crypto_aead_xchacha20poly1305_ietf_NPUBBYTES; 114 | if (message_len > ciphertext.size()) // overflow 115 | throw decrypt_error{"Decryption failed: ciphertext is too short"}; 116 | 117 | ustring_view nonce = ustring_view{ciphertext}.substr( 118 | ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); 119 | auto key = make_encrypt_key(key_base, message_len, domain); 120 | 121 | unsigned long long mlen_wrote = 0; 122 | if (0 != crypto_aead_xchacha20poly1305_ietf_decrypt( 123 | ciphertext.data(), 124 | &mlen_wrote, 125 | nullptr, 126 | ciphertext.data(), 127 | ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, 128 | nullptr, 129 | 0, 130 | nonce.data(), 131 | key.data())) 132 | throw decrypt_error{"Message decryption failed"}; 133 | 134 | assert(mlen_wrote == message_len); 135 | ciphertext.resize(mlen_wrote); 136 | } 137 | 138 | void pad_message(ustring& data, size_t overhead) { 139 | size_t target_size = padded_size(data.size(), overhead); 140 | if (target_size > data.size()) 141 | data.insert(0, target_size - data.size(), '\0'); 142 | } 143 | 144 | } // namespace session::config 145 | 146 | extern "C" { 147 | 148 | using session::ustring; 149 | 150 | LIBSESSION_EXPORT unsigned char* config_encrypt( 151 | const unsigned char* plaintext, 152 | size_t len, 153 | const unsigned char* key_base, 154 | const char* domain, 155 | size_t* ciphertext_size) { 156 | 157 | ustring ciphertext; 158 | try { 159 | ciphertext = session::config::encrypt({plaintext, len}, {key_base, 32}, domain); 160 | } catch (...) { 161 | return nullptr; 162 | } 163 | 164 | auto* data = static_cast(std::malloc(ciphertext.size())); 165 | std::memcpy(data, ciphertext.data(), ciphertext.size()); 166 | *ciphertext_size = ciphertext.size(); 167 | return data; 168 | } 169 | 170 | LIBSESSION_EXPORT unsigned char* config_decrypt( 171 | const unsigned char* ciphertext, 172 | size_t clen, 173 | const unsigned char* key_base, 174 | const char* domain, 175 | size_t* plaintext_size) { 176 | 177 | ustring plaintext; 178 | try { 179 | plaintext = session::config::decrypt({ciphertext, clen}, {key_base, 32}, domain); 180 | } catch (const std::exception& e) { 181 | return nullptr; 182 | } 183 | 184 | auto* data = static_cast(std::malloc(plaintext.size())); 185 | std::memcpy(data, plaintext.data(), plaintext.size()); 186 | *plaintext_size = plaintext.size(); 187 | return data; 188 | } 189 | 190 | LIBSESSION_EXPORT size_t config_padded_size(size_t s, size_t overhead) { 191 | return session::config::padded_size(s, overhead); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/config/error.c: -------------------------------------------------------------------------------- 1 | #include "session/config/error.h" 2 | 3 | const char* config_errstr(int err) { 4 | switch (err) { 5 | case SESSION_ERR_INVALID_DUMP: return "Dumped data is invalid"; 6 | case SESSION_ERR_BAD_VALUE: return "Invalid value"; 7 | } 8 | return "Unknown error"; 9 | } 10 | -------------------------------------------------------------------------------- /src/config/user_profile.cpp: -------------------------------------------------------------------------------- 1 | #include "session/config/user_profile.h" 2 | 3 | #include 4 | 5 | #include "internal.hpp" 6 | #include "session/config/contacts.hpp" 7 | #include "session/config/error.h" 8 | #include "session/config/user_profile.hpp" 9 | #include "session/export.h" 10 | #include "session/types.hpp" 11 | 12 | using namespace session::config; 13 | using session::ustring_view; 14 | 15 | LIBSESSION_C_API const size_t PROFILE_PIC_MAX_URL_LENGTH = profile_pic::MAX_URL_LENGTH; 16 | 17 | UserProfile::UserProfile(ustring_view ed25519_secretkey, std::optional dumped) : 18 | ConfigBase{dumped} { 19 | load_key(ed25519_secretkey); 20 | } 21 | 22 | LIBSESSION_C_API int user_profile_init( 23 | config_object** conf, 24 | const unsigned char* ed25519_secretkey_bytes, 25 | const unsigned char* dumpstr, 26 | size_t dumplen, 27 | char* error) { 28 | return c_wrapper_init(conf, ed25519_secretkey_bytes, dumpstr, dumplen, error); 29 | } 30 | 31 | std::optional UserProfile::get_name() const { 32 | if (auto* s = data["n"].string(); s && !s->empty()) 33 | return *s; 34 | return std::nullopt; 35 | } 36 | LIBSESSION_C_API const char* user_profile_get_name(const config_object* conf) { 37 | if (auto s = unbox(conf)->get_name()) 38 | return s->data(); 39 | return nullptr; 40 | } 41 | 42 | void UserProfile::set_name(std::string_view new_name) { 43 | if (new_name.size() > contact_info::MAX_NAME_LENGTH) 44 | throw std::invalid_argument{"Invalid profile name: exceeds maximum length"}; 45 | set_nonempty_str(data["n"], new_name); 46 | } 47 | void UserProfile::set_name_truncated(std::string new_name) { 48 | set_name(utf8_truncate(std::move(new_name), contact_info::MAX_NAME_LENGTH)); 49 | } 50 | LIBSESSION_C_API int user_profile_set_name(config_object* conf, const char* name) { 51 | try { 52 | unbox(conf)->set_name(name); 53 | } catch (const std::exception& e) { 54 | return set_error(conf, SESSION_ERR_BAD_VALUE, e); 55 | } 56 | return 0; 57 | } 58 | 59 | profile_pic UserProfile::get_profile_pic() const { 60 | profile_pic pic{}; 61 | if (auto* url = data["p"].string(); url && !url->empty()) 62 | pic.url = *url; 63 | if (auto* key = data["q"].string(); key && key->size() == 32) 64 | pic.key = {reinterpret_cast(key->data()), 32}; 65 | return pic; 66 | } 67 | 68 | LIBSESSION_C_API user_profile_pic user_profile_get_pic(const config_object* conf) { 69 | user_profile_pic p; 70 | if (auto pic = unbox(conf)->get_profile_pic(); pic) { 71 | copy_c_str(p.url, pic.url); 72 | std::memcpy(p.key, pic.key.data(), 32); 73 | } else { 74 | p.url[0] = 0; 75 | } 76 | return p; 77 | } 78 | 79 | void UserProfile::set_profile_pic(std::string_view url, ustring_view key) { 80 | set_pair_if(!url.empty() && key.size() == 32, data["p"], url, data["q"], key); 81 | } 82 | 83 | void UserProfile::set_profile_pic(profile_pic pic) { 84 | set_profile_pic(pic.url, pic.key); 85 | } 86 | 87 | LIBSESSION_C_API int user_profile_set_pic(config_object* conf, user_profile_pic pic) { 88 | std::string_view url{pic.url}; 89 | ustring_view key; 90 | if (!url.empty()) 91 | key = {pic.key, 32}; 92 | 93 | try { 94 | unbox(conf)->set_profile_pic(url, key); 95 | } catch (const std::exception& e) { 96 | return set_error(conf, SESSION_ERR_BAD_VALUE, e); 97 | } 98 | 99 | return 0; 100 | } 101 | 102 | void UserProfile::set_nts_priority(int priority) { 103 | set_nonzero_int(data["+"], priority); 104 | } 105 | 106 | int UserProfile::get_nts_priority() const { 107 | return data["+"].integer_or(0); 108 | } 109 | 110 | LIBSESSION_C_API int user_profile_get_nts_priority(const config_object* conf) { 111 | return unbox(conf)->get_nts_priority(); 112 | } 113 | 114 | LIBSESSION_C_API void user_profile_set_nts_priority(config_object* conf, int priority) { 115 | unbox(conf)->set_nts_priority(priority); 116 | } 117 | 118 | void UserProfile::set_nts_expiry(std::chrono::seconds expiry) { 119 | set_positive_int(data["e"], expiry.count()); 120 | } 121 | 122 | std::optional UserProfile::get_nts_expiry() const { 123 | if (auto* e = data["e"].integer(); e && *e > 0) 124 | return std::chrono::seconds{*e}; 125 | return std::nullopt; 126 | } 127 | 128 | LIBSESSION_C_API int user_profile_get_nts_expiry(const config_object* conf) { 129 | return unbox(conf)->get_nts_expiry().value_or(0s).count(); 130 | } 131 | 132 | LIBSESSION_C_API void user_profile_set_nts_expiry(config_object* conf, int expiry) { 133 | unbox(conf)->set_nts_expiry(std::max(0, expiry) * 1s); 134 | } 135 | 136 | void UserProfile::set_blinded_msgreqs(std::optional value) { 137 | if (!value) 138 | data["M"].erase(); 139 | else 140 | data["M"] = static_cast(*value); 141 | } 142 | 143 | std::optional UserProfile::get_blinded_msgreqs() const { 144 | if (auto* M = data["M"].integer(); M) 145 | return static_cast(*M); 146 | return std::nullopt; 147 | } 148 | 149 | LIBSESSION_C_API int user_profile_get_blinded_msgreqs(const config_object* conf) { 150 | if (auto opt = unbox(conf)->get_blinded_msgreqs()) 151 | return static_cast(*opt); 152 | return -1; 153 | } 154 | 155 | LIBSESSION_C_API void user_profile_set_blinded_msgreqs(config_object* conf, int enabled) { 156 | std::optional val; 157 | if (enabled >= 0) 158 | val = static_cast(enabled); 159 | unbox(conf)->set_blinded_msgreqs(std::move(val)); 160 | } 161 | -------------------------------------------------------------------------------- /src/curve25519.cpp: -------------------------------------------------------------------------------- 1 | #include "session/curve25519.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "session/export.h" 9 | #include "session/util.hpp" 10 | 11 | namespace session::curve25519 { 12 | 13 | std::pair, std::array> curve25519_key_pair() { 14 | std::array curve_pk; 15 | std::array curve_sk; 16 | crypto_box_keypair(curve_pk.data(), curve_sk.data()); 17 | 18 | return {curve_pk, curve_sk}; 19 | } 20 | 21 | std::array to_curve25519_pubkey(ustring_view ed25519_pubkey) { 22 | if (ed25519_pubkey.size() != 32) { 23 | throw std::invalid_argument{"Invalid ed25519_pubkey: expected 32 bytes"}; 24 | } 25 | 26 | std::array curve_pk; 27 | 28 | if (0 != crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed25519_pubkey.data())) 29 | throw std::runtime_error{ 30 | "An error occured while attempting to convert Ed25519 pubkey to curve25519; " 31 | "is the pubkey valid?"}; 32 | 33 | return curve_pk; 34 | } 35 | 36 | std::array to_curve25519_seckey(ustring_view ed25519_seckey) { 37 | if (ed25519_seckey.size() != 64) { 38 | throw std::invalid_argument{"Invalid ed25519_seckey: expected 64 bytes"}; 39 | } 40 | 41 | std::array curve_sk; 42 | if (0 != crypto_sign_ed25519_sk_to_curve25519(curve_sk.data(), ed25519_seckey.data())) 43 | throw std::runtime_error{ 44 | "An error occured while attempting to convert Ed25519 pubkey to curve25519; " 45 | "is the seckey valid?"}; 46 | 47 | return curve_sk; 48 | } 49 | 50 | } // namespace session::curve25519 51 | 52 | using namespace session; 53 | 54 | LIBSESSION_C_API bool session_curve25519_key_pair( 55 | unsigned char* curve25519_pk_out, unsigned char* curve25519_sk_out) { 56 | try { 57 | auto result = session::curve25519::curve25519_key_pair(); 58 | auto [curve_pk, curve_sk] = result; 59 | std::memcpy(curve25519_pk_out, curve_pk.data(), curve_pk.size()); 60 | std::memcpy(curve25519_sk_out, curve_sk.data(), curve_sk.size()); 61 | return true; 62 | } catch (...) { 63 | return false; 64 | } 65 | } 66 | 67 | LIBSESSION_C_API bool session_to_curve25519_pubkey( 68 | const unsigned char* ed25519_pubkey, unsigned char* curve25519_pk_out) { 69 | try { 70 | auto curve_pk = session::curve25519::to_curve25519_pubkey(ustring_view{ed25519_pubkey, 32}); 71 | std::memcpy(curve25519_pk_out, curve_pk.data(), curve_pk.size()); 72 | return true; 73 | } catch (...) { 74 | return false; 75 | } 76 | } 77 | 78 | LIBSESSION_C_API bool session_to_curve25519_seckey( 79 | const unsigned char* ed25519_seckey, unsigned char* curve25519_sk_out) { 80 | try { 81 | auto curve_sk = session::curve25519::to_curve25519_seckey(ustring_view{ed25519_seckey, 64}); 82 | std::memcpy(curve25519_sk_out, curve_sk.data(), curve_sk.size()); 83 | return true; 84 | } catch (...) { 85 | return false; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/ed25519.cpp: -------------------------------------------------------------------------------- 1 | #include "session/ed25519.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "session/export.h" 9 | #include "session/sodium_array.hpp" 10 | 11 | namespace session::ed25519 { 12 | 13 | template 14 | using cleared_array = sodium_cleared>; 15 | 16 | using uc32 = std::array; 17 | using cleared_uc64 = cleared_array<64>; 18 | 19 | std::pair, std::array> ed25519_key_pair() { 20 | std::array ed_pk; 21 | std::array ed_sk; 22 | crypto_sign_ed25519_keypair(ed_pk.data(), ed_sk.data()); 23 | 24 | return {ed_pk, ed_sk}; 25 | } 26 | 27 | std::pair, std::array> ed25519_key_pair( 28 | ustring_view ed25519_seed) { 29 | if (ed25519_seed.size() != 32) { 30 | throw std::invalid_argument{"Invalid ed25519_seed: expected 32 bytes"}; 31 | } 32 | 33 | std::array ed_pk; 34 | std::array ed_sk; 35 | 36 | crypto_sign_ed25519_seed_keypair(ed_pk.data(), ed_sk.data(), ed25519_seed.data()); 37 | 38 | return {ed_pk, ed_sk}; 39 | } 40 | 41 | std::array seed_for_ed_privkey(ustring_view ed25519_privkey) { 42 | std::array seed; 43 | 44 | if (ed25519_privkey.size() == 32 || ed25519_privkey.size() == 64) 45 | // The first 32 bytes of a 64 byte ed25519 private key are the seed, otherwise 46 | // if the provided value is 32 bytes we just assume we were given a seed 47 | std::memcpy(seed.data(), ed25519_privkey.data(), 32); 48 | else 49 | throw std::invalid_argument{"Invalid ed25519_privkey: expected 32 or 64 bytes"}; 50 | 51 | return seed; 52 | } 53 | 54 | ustring sign(ustring_view ed25519_privkey, ustring_view msg) { 55 | cleared_uc64 ed_sk_from_seed; 56 | if (ed25519_privkey.size() == 32) { 57 | uc32 ignore_pk; 58 | crypto_sign_ed25519_seed_keypair( 59 | ignore_pk.data(), ed_sk_from_seed.data(), ed25519_privkey.data()); 60 | ed25519_privkey = {ed_sk_from_seed.data(), ed_sk_from_seed.size()}; 61 | } else if (ed25519_privkey.size() != 64) { 62 | throw std::invalid_argument{"Invalid ed25519_privkey: expected 32 or 64 bytes"}; 63 | } 64 | 65 | std::array sig; 66 | if (0 != crypto_sign_ed25519_detached( 67 | sig.data(), nullptr, msg.data(), msg.size(), ed25519_privkey.data())) 68 | throw std::runtime_error{"Failed to sign; perhaps the secret key is invalid?"}; 69 | 70 | return {sig.data(), sig.size()}; 71 | } 72 | 73 | bool verify(ustring_view sig, ustring_view pubkey, ustring_view msg) { 74 | if (sig.size() != 64) 75 | throw std::invalid_argument{"Invalid sig: expected 64 bytes"}; 76 | if (pubkey.size() != 32) 77 | throw std::invalid_argument{"Invalid pubkey: expected 32 bytes"}; 78 | 79 | return (0 == 80 | crypto_sign_ed25519_verify_detached(sig.data(), msg.data(), msg.size(), pubkey.data())); 81 | } 82 | 83 | } // namespace session::ed25519 84 | 85 | using namespace session; 86 | 87 | LIBSESSION_C_API bool session_ed25519_key_pair( 88 | unsigned char* ed25519_pk_out, unsigned char* ed25519_sk_out) { 89 | try { 90 | auto result = session::ed25519::ed25519_key_pair(); 91 | auto [ed_pk, ed_sk] = result; 92 | std::memcpy(ed25519_pk_out, ed_pk.data(), ed_pk.size()); 93 | std::memcpy(ed25519_sk_out, ed_sk.data(), ed_sk.size()); 94 | return true; 95 | } catch (...) { 96 | return false; 97 | } 98 | } 99 | 100 | LIBSESSION_C_API bool session_ed25519_key_pair_seed( 101 | const unsigned char* ed25519_seed, 102 | unsigned char* ed25519_pk_out, 103 | unsigned char* ed25519_sk_out) { 104 | try { 105 | auto result = session::ed25519::ed25519_key_pair(ustring_view{ed25519_seed, 32}); 106 | auto [ed_pk, ed_sk] = result; 107 | std::memcpy(ed25519_pk_out, ed_pk.data(), ed_pk.size()); 108 | std::memcpy(ed25519_sk_out, ed_sk.data(), ed_sk.size()); 109 | return true; 110 | } catch (...) { 111 | return false; 112 | } 113 | } 114 | 115 | LIBSESSION_C_API bool session_seed_for_ed_privkey( 116 | const unsigned char* ed25519_privkey, unsigned char* ed25519_seed_out) { 117 | try { 118 | auto result = session::ed25519::seed_for_ed_privkey(ustring_view{ed25519_privkey, 64}); 119 | std::memcpy(ed25519_seed_out, result.data(), result.size()); 120 | return true; 121 | } catch (...) { 122 | return false; 123 | } 124 | } 125 | 126 | LIBSESSION_C_API bool session_ed25519_sign( 127 | const unsigned char* ed25519_privkey, 128 | const unsigned char* msg, 129 | size_t msg_len, 130 | unsigned char* ed25519_sig_out) { 131 | try { 132 | auto result = session::ed25519::sign( 133 | ustring_view{ed25519_privkey, 64}, ustring_view{msg, msg_len}); 134 | std::memcpy(ed25519_sig_out, result.data(), result.size()); 135 | return true; 136 | } catch (...) { 137 | return false; 138 | } 139 | } 140 | 141 | LIBSESSION_C_API bool session_ed25519_verify( 142 | const unsigned char* sig, 143 | const unsigned char* pubkey, 144 | const unsigned char* msg, 145 | size_t msg_len) { 146 | return session::ed25519::verify( 147 | ustring_view{sig, 64}, ustring_view{pubkey, 32}, ustring_view{msg, msg_len}); 148 | } 149 | -------------------------------------------------------------------------------- /src/fields.cpp: -------------------------------------------------------------------------------- 1 | #include "session/fields.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace session { 8 | 9 | std::string SessionID::hex() const { 10 | std::string id; 11 | id.reserve(33); 12 | id.push_back(static_cast(netid)); 13 | oxenc::to_hex(pubkey.begin(), pubkey.end(), std::back_inserter(id)); 14 | return id; 15 | } 16 | 17 | } // namespace session 18 | -------------------------------------------------------------------------------- /src/hash.cpp: -------------------------------------------------------------------------------- 1 | #include "session/hash.hpp" 2 | 3 | #include 4 | 5 | #include "session/export.h" 6 | #include "session/util.hpp" 7 | 8 | namespace session::hash { 9 | 10 | ustring hash(const size_t size, ustring_view msg, std::optional key) { 11 | if (size < crypto_generichash_blake2b_BYTES_MIN || size > crypto_generichash_blake2b_BYTES_MAX) 12 | throw std::invalid_argument{"Invalid size: expected between 16 and 64 bytes (inclusive)"}; 13 | 14 | if (key && key->size() > crypto_generichash_blake2b_BYTES_MAX) 15 | throw std::invalid_argument{"Invalid key: expected less than 65 bytes"}; 16 | 17 | ustring result; 18 | result.resize(size); 19 | crypto_generichash_blake2b( 20 | result.data(), 21 | size, 22 | msg.data(), 23 | msg.size(), 24 | key ? key->data() : nullptr, 25 | key ? key->size() : 0); 26 | 27 | return result; 28 | } 29 | 30 | } // namespace session::hash 31 | 32 | using session::ustring; 33 | using session::ustring_view; 34 | 35 | extern "C" { 36 | 37 | LIBSESSION_C_API bool session_hash( 38 | size_t size, 39 | const unsigned char* msg_in, 40 | size_t msg_len, 41 | const unsigned char* key_in, 42 | size_t key_len, 43 | unsigned char* hash_out) { 44 | try { 45 | std::optional key; 46 | 47 | if (key_in && key_len) 48 | key = {key_in, key_len}; 49 | 50 | ustring result = session::hash::hash(size, {msg_in, msg_len}, key); 51 | std::memcpy(hash_out, result.data(), size); 52 | return true; 53 | } catch (...) { 54 | return false; 55 | } 56 | } 57 | 58 | } // extern "C" 59 | -------------------------------------------------------------------------------- /src/onionreq/key_types.cpp: -------------------------------------------------------------------------------- 1 | #include "session/onionreq/key_types.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace session::onionreq { 12 | 13 | namespace detail { 14 | 15 | void load_from_hex(void* buffer, size_t length, std::string_view hex) { 16 | if (!oxenc::is_hex(hex)) 17 | throw std::runtime_error{"Hex key data is invalid: data is not hex"}; 18 | if (hex.size() != 2 * length) 19 | throw std::runtime_error{ 20 | "Hex key data is invalid: expected " + std::to_string(length) + 21 | " hex digits, received " + std::to_string(hex.size())}; 22 | oxenc::from_hex(hex.begin(), hex.end(), reinterpret_cast(buffer)); 23 | } 24 | 25 | void load_from_bytes(void* buffer, size_t length, std::string_view bytes) { 26 | if (bytes.size() != length) 27 | throw std::runtime_error{ 28 | "Key data is invalid: expected " + std::to_string(length) + 29 | " bytes, received " + std::to_string(bytes.size())}; 30 | std::memmove(buffer, bytes.data(), length); 31 | } 32 | 33 | } // namespace detail 34 | 35 | std::string ed25519_pubkey::snode_address() const { 36 | auto addr = oxenc::to_base32z(begin(), end()); 37 | addr += ".snode"; 38 | return addr; 39 | } 40 | 41 | legacy_pubkey legacy_seckey::pubkey() const { 42 | legacy_pubkey pk; 43 | crypto_scalarmult_ed25519_base_noclamp(pk.data(), data()); 44 | return pk; 45 | }; 46 | ed25519_pubkey ed25519_seckey::pubkey() const { 47 | ed25519_pubkey pk; 48 | crypto_sign_ed25519_sk_to_pk(pk.data(), data()); 49 | return pk; 50 | }; 51 | x25519_pubkey x25519_seckey::pubkey() const { 52 | x25519_pubkey pk; 53 | crypto_scalarmult_curve25519_base(pk.data(), data()); 54 | return pk; 55 | }; 56 | 57 | template 58 | static T parse_pubkey(std::string_view pubkey_in) { 59 | T pk{}; 60 | static_assert(pk.size() == 32); 61 | if (pubkey_in.size() == 32) 62 | detail::load_from_bytes(pk.data(), 32, pubkey_in); 63 | else if (pubkey_in.size() == 64 && oxenc::is_hex(pubkey_in)) 64 | oxenc::from_hex(pubkey_in.begin(), pubkey_in.end(), pk.begin()); 65 | else if ( 66 | (pubkey_in.size() == 43 || (pubkey_in.size() == 44 && pubkey_in.back() == '=')) && 67 | oxenc::is_base64(pubkey_in)) 68 | oxenc::from_base64(pubkey_in.begin(), pubkey_in.end(), pk.begin()); 69 | else if (pubkey_in.size() == 52 && oxenc::is_base32z(pubkey_in)) 70 | oxenc::from_base32z(pubkey_in.begin(), pubkey_in.end(), pk.begin()); 71 | 72 | return pk; 73 | } 74 | 75 | legacy_pubkey parse_legacy_pubkey(std::string_view pubkey_in) { 76 | return parse_pubkey(pubkey_in); 77 | } 78 | ed25519_pubkey parse_ed25519_pubkey(std::string_view pubkey_in) { 79 | return parse_pubkey(pubkey_in); 80 | } 81 | x25519_pubkey parse_x25519_pubkey(std::string_view pubkey_in) { 82 | return parse_pubkey(pubkey_in); 83 | } 84 | 85 | } // namespace session::onionreq 86 | -------------------------------------------------------------------------------- /src/onionreq/parser.cpp: -------------------------------------------------------------------------------- 1 | #include "session/onionreq/parser.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace session::onionreq { 10 | 11 | OnionReqParser::OnionReqParser( 12 | ustring_view x25519_pk, ustring_view x25519_sk, ustring_view req, size_t max_size) : 13 | keys{x25519_pubkey::from_bytes(x25519_pk), x25519_seckey::from_bytes(x25519_sk)}, 14 | enc{keys.second, keys.first} { 15 | if (sodium_init() == -1) 16 | throw std::runtime_error{"Failed to initialize libsodium!"}; 17 | if (req.size() < sizeof(uint32_t)) 18 | throw std::invalid_argument{"onion request data too small"}; 19 | if (req.size() > max_size) 20 | throw std::invalid_argument{"onion request data too big"}; 21 | auto size = oxenc::load_little_to_host(req.data()); 22 | req.remove_prefix(sizeof(size)); 23 | 24 | if (req.size() < size) 25 | throw std::invalid_argument{"encrypted onion request data segment too small"}; 26 | auto ciphertext = req.substr(0, size); 27 | req.remove_prefix(size); 28 | auto metadata = nlohmann::json::parse(req); 29 | 30 | if (auto encit = metadata.find("enc_type"); encit != metadata.end()) 31 | enc_type = parse_enc_type(encit->get()); 32 | // else leave it at the backwards-compat AES-GCM default 33 | 34 | if (auto itr = metadata.find("ephemeral_key"); itr != metadata.end()) 35 | remote_pk = parse_x25519_pubkey(itr->get()); 36 | else 37 | throw std::invalid_argument{"metadata does not have 'ephemeral_key' entry"}; 38 | 39 | auto plaintext = enc.decrypt(enc_type, {ciphertext.data(), ciphertext.size()}, remote_pk); 40 | payload_ = {to_unsigned(plaintext.data()), plaintext.size()}; 41 | } 42 | 43 | ustring OnionReqParser::encrypt_reply(ustring_view reply) const { 44 | return enc.encrypt(enc_type, {reply.data(), reply.size()}, remote_pk); 45 | } 46 | 47 | } // namespace session::onionreq 48 | -------------------------------------------------------------------------------- /src/onionreq/response_parser.cpp: -------------------------------------------------------------------------------- 1 | #include "session/onionreq/response_parser.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "session/export.h" 9 | #include "session/onionreq/builder.h" 10 | #include "session/onionreq/builder.hpp" 11 | #include "session/onionreq/hop_encryption.hpp" 12 | 13 | namespace session::onionreq { 14 | 15 | ResponseParser::ResponseParser(session::onionreq::Builder builder) { 16 | if (!builder.destination_x25519_public_key.has_value()) 17 | throw std::runtime_error{"Builder does not contain destination x25519 public key"}; 18 | if (!builder.final_hop_x25519_keypair.has_value()) 19 | throw std::runtime_error{"Builder does not contain final keypair"}; 20 | 21 | enc_type_ = builder.enc_type; 22 | destination_x25519_public_key_ = builder.destination_x25519_public_key.value(); 23 | x25519_keypair_ = builder.final_hop_x25519_keypair.value(); 24 | } 25 | 26 | ustring ResponseParser::decrypt(ustring ciphertext) const { 27 | HopEncryption d{x25519_keypair_.second, x25519_keypair_.first, false}; 28 | 29 | // FIXME: The legacy PN server doesn't support 'xchacha20' onion requests so would return an 30 | // error encrypted with 'aes_gcm' so try to decrypt in case that is what happened - this 31 | // workaround can be removed once the legacy PN server is removed 32 | try { 33 | return d.decrypt(enc_type_, ciphertext, destination_x25519_public_key_); 34 | } catch (const std::exception& e) { 35 | if (enc_type_ == session::onionreq::EncryptType::xchacha20) 36 | return d.decrypt( 37 | session::onionreq::EncryptType::aes_gcm, 38 | ciphertext, 39 | destination_x25519_public_key_); 40 | else 41 | throw e; 42 | } 43 | } 44 | 45 | } // namespace session::onionreq 46 | 47 | extern "C" { 48 | 49 | using session::ustring; 50 | 51 | LIBSESSION_C_API bool onion_request_decrypt( 52 | const unsigned char* ciphertext, 53 | size_t ciphertext_len, 54 | ENCRYPT_TYPE enc_type_, 55 | unsigned char* destination_x25519_pubkey, 56 | unsigned char* final_x25519_pubkey, 57 | unsigned char* final_x25519_seckey, 58 | unsigned char** plaintext_out, 59 | size_t* plaintext_out_len) { 60 | assert(ciphertext && destination_x25519_pubkey && final_x25519_pubkey && final_x25519_seckey && 61 | ciphertext_len > 0); 62 | 63 | try { 64 | auto enc_type = session::onionreq::EncryptType::xchacha20; 65 | 66 | switch (enc_type_) { 67 | case ENCRYPT_TYPE::ENCRYPT_TYPE_AES_GCM: 68 | enc_type = session::onionreq::EncryptType::aes_gcm; 69 | break; 70 | 71 | case ENCRYPT_TYPE::ENCRYPT_TYPE_X_CHA_CHA_20: 72 | enc_type = session::onionreq::EncryptType::xchacha20; 73 | break; 74 | 75 | default: 76 | throw std::runtime_error{"Invalid decryption type " + std::to_string(enc_type_)}; 77 | } 78 | 79 | session::onionreq::HopEncryption d{ 80 | session::onionreq::x25519_seckey::from_bytes({final_x25519_seckey, 32}), 81 | session::onionreq::x25519_pubkey::from_bytes({final_x25519_pubkey, 32}), 82 | false}; 83 | 84 | ustring result; 85 | 86 | // FIXME: The legacy PN server doesn't support 'xchacha20' onion requests so would return an 87 | // error encrypted with 'aes_gcm' so try to decrypt in case that is what happened - this 88 | // workaround can be removed once the legacy PN server is removed 89 | try { 90 | result = d.decrypt( 91 | enc_type, 92 | ustring{ciphertext, ciphertext_len}, 93 | session::onionreq::x25519_pubkey::from_bytes({destination_x25519_pubkey, 32})); 94 | } catch (...) { 95 | if (enc_type == session::onionreq::EncryptType::xchacha20) 96 | result = d.decrypt( 97 | session::onionreq::EncryptType::aes_gcm, 98 | ustring{ciphertext, ciphertext_len}, 99 | session::onionreq::x25519_pubkey::from_bytes( 100 | {destination_x25519_pubkey, 32})); 101 | else 102 | return false; 103 | } 104 | 105 | *plaintext_out = static_cast(malloc(result.size())); 106 | *plaintext_out_len = result.size(); 107 | std::memcpy(*plaintext_out, result.data(), result.size()); 108 | return true; 109 | } catch (...) { 110 | return false; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/random.cpp: -------------------------------------------------------------------------------- 1 | #include "session/random.hpp" 2 | 3 | #include 4 | 5 | #include "session/export.h" 6 | #include "session/util.hpp" 7 | 8 | namespace session::random { 9 | 10 | ustring random(size_t size) { 11 | ustring result; 12 | result.resize(size); 13 | randombytes_buf(result.data(), size); 14 | 15 | return result; 16 | } 17 | 18 | } // namespace session::random 19 | 20 | extern "C" { 21 | 22 | LIBSESSION_C_API unsigned char* session_random(size_t size) { 23 | auto result = session::random::random(size); 24 | auto* ret = static_cast(malloc(size)); 25 | std::memcpy(ret, result.data(), result.size()); 26 | return ret; 27 | } 28 | 29 | } // extern "C" 30 | -------------------------------------------------------------------------------- /src/sodium_array.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace session { 6 | 7 | void* sodium_buffer_allocate(size_t length) { 8 | if (auto* p = sodium_malloc(length)) 9 | return p; 10 | throw std::bad_alloc{}; 11 | } 12 | 13 | void sodium_buffer_deallocate(void* p) { 14 | if (p) 15 | sodium_free(p); 16 | } 17 | 18 | void sodium_zero_buffer(void* ptr, size_t size) { 19 | if (ptr) 20 | sodium_memzero(ptr, size); 21 | } 22 | 23 | } // namespace session 24 | -------------------------------------------------------------------------------- /src/util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace session { 6 | 7 | } // namespace session 8 | -------------------------------------------------------------------------------- /src/version.c.in: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | const uint16_t LIBSESSION_UTIL_VERSION[3] = 4 | {@PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@}; 5 | 6 | const char* LIBSESSION_UTIL_VERSION_FULL = 7 | "libsession-util v@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@-@PROJECT_VERSION_TAG@"; 8 | 9 | const char* LIBSESSION_UTIL_VERSION_STR = 10 | "v@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@-@PROJECT_VERSION_TAG@"; 11 | -------------------------------------------------------------------------------- /src/xed25519.cpp: -------------------------------------------------------------------------------- 1 | #include "session/xed25519.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "session/export.h" 15 | #include "session/util.hpp" 16 | 17 | namespace session::xed25519 { 18 | 19 | template 20 | using bytes = std::array; 21 | 22 | namespace { 23 | 24 | void fe25519_montx_to_edy(fe25519 y, const fe25519 u) { 25 | fe25519 one; 26 | crypto_internal_fe25519_1(one); 27 | fe25519 um1, up1; 28 | crypto_internal_fe25519_sub(um1, u, one); 29 | crypto_internal_fe25519_add(up1, u, one); 30 | crypto_internal_fe25519_invert(up1, up1); 31 | crypto_internal_fe25519_mul(y, um1, up1); 32 | } 33 | 34 | // We construct an Ed25519-like signature with one important difference: where Ed25519 35 | // calculates `r = H(S || M) mod L` (where S is the second half of the SHA-512 hash of the 36 | // secret key) we instead calculate `r = H(a || M || Z) mod L`. 37 | // 38 | // This deviates from Signal's XEd25519 specified derivation of r in that we use a personalized 39 | // Black2b hash (for better performance and cryptographic properties), rather than a 40 | // custom-prefixed SHA-512 hash. 41 | bytes<32> xed25519_compute_r(const bytes<32>& a, ustring_view msg) { 42 | bytes<64> random; 43 | randombytes_buf(random.data(), random.size()); 44 | 45 | constexpr static bytes<16> personality = { 46 | 'x', 'e', 'd', '2', '5', '5', '1', '9', 's', 'i', 'g', 'n', 'a', 't', 'u', 'r'}; 47 | 48 | crypto_generichash_blake2b_state st; 49 | static_assert(personality.size() == crypto_generichash_blake2b_PERSONALBYTES); 50 | crypto_generichash_blake2b_init_salt_personal( 51 | &st, nullptr, 0, 64, nullptr, personality.data()); 52 | crypto_generichash_blake2b_update(&st, a.data(), a.size()); 53 | crypto_generichash_blake2b_update(&st, msg.data(), msg.size()); 54 | crypto_generichash_blake2b_update(&st, random.data(), random.size()); 55 | bytes<64> h_aMZ; 56 | crypto_generichash_blake2b_final(&st, h_aMZ.data(), h_aMZ.size()); 57 | 58 | bytes<32> r; 59 | crypto_core_ed25519_scalar_reduce(r.data(), h_aMZ.data()); 60 | return r; 61 | } 62 | 63 | // Assigns S = H(R || A || M) mod L 64 | void ed25519_hram( 65 | unsigned char* S, const unsigned char* R, const bytes<32>& A, ustring_view msg) { 66 | bytes<64> hram; 67 | crypto_hash_sha512_state st; 68 | crypto_hash_sha512_init(&st); 69 | crypto_hash_sha512_update(&st, R, 32); 70 | crypto_hash_sha512_update(&st, A.data(), A.size()); 71 | crypto_hash_sha512_update(&st, msg.data(), msg.size()); 72 | crypto_hash_sha512_final(&st, hram.data()); 73 | 74 | crypto_core_ed25519_scalar_reduce(S, hram.data()); 75 | } 76 | 77 | ustring_view as_unsigned_sv(std::string_view x) { 78 | return {reinterpret_cast(x.data()), x.size()}; 79 | } 80 | 81 | } // namespace 82 | 83 | bytes<64> sign(ustring_view curve25519_privkey, ustring_view msg) { 84 | 85 | assert(curve25519_privkey.size() == 32); 86 | 87 | bytes<32> A; 88 | // Convert the x25519 privkey to an ed25519 pubkey: 89 | crypto_scalarmult_ed25519_base(A.data(), curve25519_privkey.data()); 90 | 91 | // Signal's XEd25519 spec requires that the sign bit be zero, so if it isn't we negate. 92 | bool negative = A[31] >> 7; 93 | 94 | A[31] &= 0x7f; 95 | 96 | bytes<32> a, neg_a; 97 | std::memcpy(a.data(), curve25519_privkey.data(), a.size()); 98 | crypto_core_ed25519_scalar_negate(neg_a.data(), a.data()); 99 | constant_time_conditional_assign(a, neg_a, negative); 100 | 101 | // We now have our a, A privkey/public. (Note that a is just the private key scalar, *not* the 102 | // ed25519 secret key). 103 | 104 | bytes<32> r = xed25519_compute_r(a, msg); 105 | bytes<64> signature; // R || S 106 | auto* R = signature.data(); 107 | auto* S = signature.data() + 32; 108 | 109 | crypto_scalarmult_ed25519_base_noclamp(R, r.data()); 110 | 111 | // Now we have compute S = r + H(R || A || M)a 112 | ed25519_hram(S, R, A, msg); // S = H(R||A||M) 113 | crypto_core_ed25519_scalar_mul(S, S, a.data()); // S *= a 114 | crypto_core_ed25519_scalar_add(S, S, r.data()); // S += r 115 | 116 | return signature; 117 | } 118 | 119 | std::string sign(std::string_view curve25519_privkey, std::string_view msg) { 120 | auto sig = sign(as_unsigned_sv(curve25519_privkey), as_unsigned_sv(msg)); 121 | return std::string{reinterpret_cast(sig.data()), sig.size()}; 122 | } 123 | 124 | bool verify(ustring_view signature, ustring_view curve25519_pubkey, ustring_view msg) { 125 | assert(signature.size() == crypto_sign_ed25519_BYTES); 126 | assert(curve25519_pubkey.size() == 32); 127 | auto ed_pubkey = pubkey(curve25519_pubkey); 128 | return 0 == crypto_sign_ed25519_verify_detached( 129 | signature.data(), msg.data(), msg.size(), ed_pubkey.data()); 130 | } 131 | 132 | bool verify(std::string_view signature, std::string_view curve25519_pubkey, std::string_view msg) { 133 | return verify( 134 | as_unsigned_sv(signature), as_unsigned_sv(curve25519_pubkey), as_unsigned_sv(msg)); 135 | } 136 | 137 | std::array pubkey(ustring_view curve25519_pubkey) { 138 | fe25519 u, y; 139 | crypto_internal_fe25519_frombytes(u, curve25519_pubkey.data()); 140 | fe25519_montx_to_edy(y, u); 141 | 142 | std::array ed_pubkey; 143 | crypto_internal_fe25519_tobytes(ed_pubkey.data(), y); 144 | 145 | return ed_pubkey; 146 | } 147 | 148 | std::string pubkey(std::string_view curve25519_pubkey) { 149 | auto ed_pk = pubkey(as_unsigned_sv(curve25519_pubkey)); 150 | return std::string{reinterpret_cast(ed_pk.data()), ed_pk.size()}; 151 | } 152 | 153 | } // namespace session::xed25519 154 | 155 | using session::xed25519::ustring_view; 156 | 157 | extern "C" { 158 | 159 | LIBSESSION_C_API bool session_xed25519_sign( 160 | unsigned char* signature, 161 | const unsigned char* curve25519_privkey, 162 | const unsigned char* msg, 163 | size_t msg_len) { 164 | assert(signature != NULL); 165 | try { 166 | auto sig = session::xed25519::sign({curve25519_privkey, 32}, {msg, msg_len}); 167 | std::memcpy(signature, sig.data(), sig.size()); 168 | return true; 169 | } catch (...) { 170 | return false; 171 | } 172 | } 173 | 174 | LIBSESSION_C_API bool session_xed25519_verify( 175 | const unsigned char* signature, 176 | const unsigned char* pubkey, 177 | const unsigned char* msg, 178 | size_t msg_len) { 179 | return session::xed25519::verify({signature, 64}, {pubkey, 32}, {msg, msg_len}); 180 | } 181 | 182 | LIBSESSION_C_API bool session_xed25519_pubkey( 183 | unsigned char* ed25519_pubkey, const unsigned char* curve25519_pubkey) { 184 | assert(ed25519_pubkey != NULL); 185 | try { 186 | auto edpk = session::xed25519::pubkey({curve25519_pubkey, 32}); 187 | std::memcpy(ed25519_pubkey, edpk.data(), edpk.size()); 188 | return true; 189 | } catch (...) { 190 | return false; 191 | } 192 | } 193 | 194 | } // extern "C" 195 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(Catch2) 2 | 3 | add_executable(testAll 4 | test_blinding.cpp 5 | test_bt_merge.cpp 6 | test_bugs.cpp 7 | test_compression.cpp 8 | test_config_userprofile.cpp 9 | test_config_user_groups.cpp 10 | test_configdata.cpp 11 | test_config_contacts.cpp 12 | test_config_convo_info_volatile.cpp 13 | test_curve25519.cpp 14 | test_ed25519.cpp 15 | test_encrypt.cpp 16 | test_group_keys.cpp 17 | test_group_info.cpp 18 | test_group_members.cpp 19 | test_hash.cpp 20 | test_multi_encrypt.cpp 21 | test_onionreq.cpp 22 | test_proto.cpp 23 | test_random.cpp 24 | test_session_encrypt.cpp 25 | test_xed25519.cpp 26 | ) 27 | 28 | 29 | target_link_libraries(testAll PRIVATE 30 | libsession::config 31 | libsodium::sodium-internal 32 | Catch2::Catch2WithMain 33 | ) 34 | 35 | if (ENABLE_ONIONREQ) 36 | target_link_libraries(testAll PRIVATE libsession::onionreq) 37 | endif() 38 | 39 | add_custom_target(check COMMAND testAll) 40 | 41 | add_executable(swarm-auth-test EXCLUDE_FROM_ALL swarm-auth-test.cpp) 42 | target_link_libraries(swarm-auth-test PRIVATE config) 43 | 44 | if(STATIC_BUNDLE) 45 | add_executable(static-bundle-test static_bundle.cpp) 46 | target_include_directories(static-bundle-test PUBLIC ../include) 47 | target_link_libraries(static-bundle-test PRIVATE "${PROJECT_BINARY_DIR}/libsession-util.a" oxenc::oxenc) 48 | add_dependencies(static-bundle-test session-util) 49 | endif() 50 | -------------------------------------------------------------------------------- /tests/catch2_bt_format.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace Catch { 9 | 10 | template <> 11 | struct StringMaker { 12 | static std::string convert(const oxenc::bt_value& value); 13 | }; 14 | 15 | template <> 16 | struct StringMaker { 17 | static std::string convert(const oxenc::bt_list& value); 18 | }; 19 | template <> 20 | struct StringMaker { 21 | static std::string convert(const oxenc::bt_dict& value); 22 | }; 23 | 24 | inline std::string StringMaker::convert(const oxenc::bt_value& value) { 25 | return var::visit( 26 | [](const auto& x) { 27 | return StringMaker>{}.convert(x); 28 | }, 29 | static_cast(value)); 30 | } 31 | inline std::string StringMaker::convert(const oxenc::bt_list& value) { 32 | std::string r = "["; 33 | for (auto& el : value) { 34 | r += StringMaker{}.convert(el); 35 | r += ", "; 36 | } 37 | if (!value.empty()) { 38 | r.pop_back(); 39 | r.back() = ']'; 40 | } 41 | return r; 42 | } 43 | 44 | inline std::string StringMaker::convert(const oxenc::bt_dict& value) { 45 | std::string r = "{ "; 46 | for (auto& [key, val] : value) { 47 | r += key; 48 | r += ": "; 49 | r += StringMaker{}.convert(val); 50 | r += ", "; 51 | } 52 | if (!value.empty()) { 53 | *(r.rbegin() + 1) = ' '; 54 | } 55 | r.back() = '}'; 56 | 57 | return r; 58 | } 59 | 60 | } // namespace Catch 61 | -------------------------------------------------------------------------------- /tests/static_bundle.cpp: -------------------------------------------------------------------------------- 1 | // This file isn't designed to do anything useful, but just to test that we can compile and link 2 | // against the combined static bundle (when using cmake ... -DSTATIC_BUILD=ON) 3 | 4 | #include 5 | #include 6 | 7 | int main() { 8 | if (std::mt19937_64{}() == 123) { 9 | auto& k = *reinterpret_cast(12345); 10 | k.encrypt_message(session::ustring_view{}); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/swarm-auth-test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "utils.hpp" 23 | 24 | using namespace std::literals; 25 | using namespace oxenc::literals; 26 | 27 | static constexpr int64_t created_ts = 1680064059; 28 | 29 | using namespace session::config; 30 | 31 | static std::array sk_from_seed(ustring_view seed) { 32 | std::array ignore; 33 | std::array sk; 34 | crypto_sign_ed25519_seed_keypair(ignore.data(), sk.data(), seed.data()); 35 | return sk; 36 | } 37 | 38 | static std::string session_id_from_ed(ustring_view ed_pk) { 39 | std::string sid; 40 | std::array xpk; 41 | int rc = crypto_sign_ed25519_pk_to_curve25519(xpk.data(), ed_pk.data()); 42 | assert(rc == 0); 43 | sid.reserve(66); 44 | sid += "05"; 45 | oxenc::to_hex(xpk.begin(), xpk.end(), std::back_inserter(sid)); 46 | return sid; 47 | } 48 | 49 | struct pseudo_client { 50 | std::array secret_key; 51 | const ustring_view public_key{secret_key.data() + 32, 32}; 52 | std::string session_id{session_id_from_ed(public_key)}; 53 | 54 | groups::Info info; 55 | groups::Members members; 56 | groups::Keys keys; 57 | 58 | pseudo_client( 59 | ustring_view seed, 60 | bool admin, 61 | const unsigned char* gpk, 62 | std::optional gsk) : 63 | secret_key{sk_from_seed(seed)}, 64 | info{ustring_view{gpk, 32}, 65 | admin ? std::make_optional({*gsk, 64}) : std::nullopt, 66 | std::nullopt}, 67 | members{ustring_view{gpk, 32}, 68 | admin ? std::make_optional({*gsk, 64}) : std::nullopt, 69 | std::nullopt}, 70 | keys{to_usv(secret_key), 71 | ustring_view{gpk, 32}, 72 | admin ? std::make_optional({*gsk, 64}) : std::nullopt, 73 | std::nullopt, 74 | info, 75 | members} {} 76 | }; 77 | 78 | int main() { 79 | 80 | const ustring group_seed = 81 | "0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210"_hexbytes; 82 | const ustring admin_seed = 83 | "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; 84 | const ustring member_seed = 85 | "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"_hexbytes; 86 | 87 | std::array group_pk; 88 | std::array group_sk; 89 | 90 | crypto_sign_ed25519_seed_keypair(group_pk.data(), group_sk.data(), group_seed.data()); 91 | 92 | pseudo_client admin{admin_seed, true, group_pk.data(), group_sk.data()}; 93 | pseudo_client member{member_seed, false, group_pk.data(), std::nullopt}; 94 | session::config::UserGroups member_groups{member_seed, std::nullopt}; 95 | 96 | auto auth_data = admin.keys.swarm_make_subaccount(member.session_id); 97 | { 98 | auto g = member_groups.get_or_construct_group(member.info.id); 99 | g.auth_data = auth_data; 100 | member_groups.set(g); 101 | } 102 | 103 | session::config::UserGroups member_gr2{member_seed, std::nullopt}; 104 | auto [seqno, push, obs] = member_groups.push(); 105 | 106 | std::vector> gr_conf; 107 | gr_conf.emplace_back("fakehash1", push); 108 | 109 | member_gr2.merge(gr_conf); 110 | 111 | auto now = std::chrono::duration_cast( 112 | std::chrono::system_clock::now().time_since_epoch()) 113 | .count(); 114 | 115 | auto msg = to_usv("hello world"); 116 | std::array store_sig; 117 | ustring store_to_sign; 118 | store_to_sign += to_usv("store999"); 119 | store_to_sign += to_usv(std::to_string(now)); 120 | crypto_sign_ed25519_detached( 121 | store_sig.data(), nullptr, store_to_sign.data(), store_to_sign.size(), group_sk.data()); 122 | 123 | nlohmann::json store{ 124 | {"method", "store"}, 125 | {"params", 126 | {{"pubkey", member.info.id}, 127 | {"namespace", 999}, 128 | {"timestamp", now}, 129 | {"ttl", 3600'000}, 130 | {"data", oxenc::to_base64(msg)}, 131 | {"signature", oxenc::to_base64(store_sig.begin(), store_sig.end())}}}}; 132 | 133 | std::cout << "STORE:\n\n" << store.dump() << "\n\n"; 134 | 135 | ustring retrieve_to_sign; 136 | retrieve_to_sign += to_usv("retrieve999"); 137 | retrieve_to_sign += to_usv(std::to_string(now)); 138 | auto subauth = member.keys.swarm_subaccount_sign(retrieve_to_sign, auth_data); 139 | 140 | nlohmann::json retrieve{ 141 | {"method", "retrieve"}, 142 | {"params", 143 | { 144 | {"pubkey", member.info.id}, 145 | {"namespace", 999}, 146 | {"timestamp", now}, 147 | {"subaccount", subauth.subaccount}, 148 | {"subaccount_sig", subauth.subaccount_sig}, 149 | {"signature", subauth.signature}, 150 | }}}; 151 | 152 | std::cout << "RETRIEVE:\n\n" << retrieve.dump() << "\n\n"; 153 | } 154 | -------------------------------------------------------------------------------- /tests/test_bt_merge.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "catch2_bt_format.hpp" 4 | #include "session/bt_merge.hpp" 5 | 6 | using oxenc::bt_dict; 7 | using oxenc::bt_list; 8 | using oxenc::bt_value; 9 | 10 | TEST_CASE("bt_dict merging", "[bt_dict][merge]") { 11 | bt_dict x{{"a", 1}, {"b", 2}, {"c", 3}}; 12 | bt_dict y{{"a", 42}, {"b", -123}, {"x", 12}, {"y", 17}, {"Z", 4}}; 13 | 14 | CHECK(session::bt::merge(x, y) == 15 | bt_dict{{"a", 1}, {"b", 2}, {"c", 3}, {"x", 12}, {"y", 17}, {"Z", 4}}); 16 | 17 | CHECK(session::bt::merge(y, x) == 18 | bt_dict{{"a", 42}, {"b", -123}, {"c", 3}, {"x", 12}, {"y", 17}, {"Z", 4}}); 19 | 20 | CHECK(session::bt::merge(x, bt_dict{}) == x); 21 | CHECK(session::bt::merge(bt_dict{}, x) == x); 22 | CHECK(session::bt::merge(y, bt_dict{}) == y); 23 | CHECK(session::bt::merge(bt_dict{}, y) == y); 24 | CHECK(session::bt::merge(bt_dict{}, bt_dict{}) == bt_dict{}); 25 | } 26 | 27 | TEST_CASE("bt_list sorted merge", "[bt_list][merge]") { 28 | bt_list x{1, 2, 3, 5, 8, 13, 21}; 29 | bt_list y{2, 4, 8, 16}; 30 | 31 | auto compare = [](const auto& a, const auto& b) { 32 | return var::get(a) < var::get(b); 33 | }; 34 | 35 | CHECK(session::bt::merge_sorted(x, y, compare) == bt_list{1, 2, 3, 4, 5, 8, 13, 16, 21}); 36 | 37 | CHECK(session::bt::merge_sorted(x, y, compare, true) == 38 | bt_list{1, 2, 2, 3, 4, 5, 8, 8, 13, 16, 21}); 39 | 40 | CHECK(session::bt::merge_sorted(bt_list{1, 2}, bt_list{2}, compare) == bt_list{1, 2}); 41 | CHECK(session::bt::merge_sorted(bt_list{1, 2}, bt_list{2}, compare, true) == bt_list{1, 2, 2}); 42 | CHECK(session::bt::merge_sorted(bt_list{2}, bt_list{2}, compare) == bt_list{2}); 43 | CHECK(session::bt::merge_sorted(bt_list{}, bt_list{2}, compare) == bt_list{2}); 44 | CHECK(session::bt::merge_sorted(bt_list{2}, bt_list{}, compare) == bt_list{2}); 45 | CHECK(session::bt::merge_sorted(bt_list{}, bt_list{}, compare) == bt_list{}); 46 | CHECK(session::bt::merge_sorted(bt_list{}, bt_list{}, compare, true) == bt_list{}); 47 | } 48 | -------------------------------------------------------------------------------- /tests/test_bugs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "utils.hpp" 7 | 8 | using namespace session::config; 9 | 10 | TEST_CASE("Dirty/Mutable test case", "[config][dirty]") { 11 | 12 | const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; 13 | std::array ed_pk, curve_pk; 14 | std::array ed_sk; 15 | crypto_sign_ed25519_seed_keypair( 16 | ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); 17 | int rc = crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data()); 18 | REQUIRE(rc == 0); 19 | 20 | REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == 21 | "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); 22 | REQUIRE(oxenc::to_hex(curve_pk.begin(), curve_pk.end()) == 23 | "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); 24 | CHECK(oxenc::to_hex(seed.begin(), seed.end()) == 25 | oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); 26 | 27 | session::config::Contacts c1{ustring_view{seed}, std::nullopt}; 28 | c1.set_name("050000000000000000000000000000000000000000000000000000000000000000", "alfonso"); 29 | auto [seqno, data, obsolete] = c1.push(); 30 | CHECK(obsolete == std::vector{}); 31 | c1.confirm_pushed(seqno, "fakehash1"); 32 | 33 | session::config::Contacts c2{ustring_view{seed}, c1.dump()}; 34 | session::config::Contacts c3{ustring_view{seed}, c1.dump()}; 35 | 36 | CHECK_FALSE(c2.needs_dump()); 37 | CHECK_FALSE(c2.needs_push()); 38 | CHECK_FALSE(c3.needs_dump()); 39 | CHECK_FALSE(c3.needs_push()); 40 | 41 | c2.set_name("051111111111111111111111111111111111111111111111111111111111111111", "barney"); 42 | c3.set_name( 43 | "052222222222222222222222222222222222222222222222222222222222222222", "chalmondeley"); 44 | 45 | auto [seqno2, data2, obs2] = c2.push(); 46 | auto [seqno3, data3, obs3] = c3.push(); 47 | 48 | REQUIRE(seqno2 == 2); 49 | CHECK(obs2 == std::vector{"fakehash1"s}); 50 | REQUIRE(seqno3 == 2); 51 | CHECK(obs2 == std::vector{"fakehash1"s}); 52 | 53 | auto r = c1.merge(std::vector>{ 54 | {{"fakehash2", data2}, {"fakehash3", data3}}}); 55 | CHECK(r == std::vector{{"fakehash2"s, "fakehash3"s}}); 56 | CHECK(c1.needs_dump()); 57 | CHECK(c1.needs_push()); // because we have the merge conflict to push 58 | CHECK(c1.is_dirty()); 59 | CHECK(!c1.is_clean()); 60 | 61 | c1.set_name("053333333333333333333333333333333333333333333333333333333333333333", "elly"); 62 | 63 | CHECK(c1.needs_dump()); 64 | CHECK(c1.needs_push()); // because we have the merge conflict to push 65 | auto [seqno4, data4, obs4] = c1.push(); 66 | CHECK(!c1.is_dirty()); 67 | CHECK(!c1.is_clean()); // not clean yet because we haven't confirmed 68 | 69 | CHECK(seqno4 == 3); // The merge *and* change should go into the same message update/seqno 70 | CHECK(as_set(obs4) == make_set("fakehash1"s, "fakehash2"s, "fakehash3"s)); 71 | } 72 | -------------------------------------------------------------------------------- /tests/test_compression.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "utils.hpp" 11 | 12 | namespace session::config { 13 | void compress_message(ustring& msg, int level); 14 | } 15 | 16 | using namespace std::literals; 17 | using namespace oxenc::literals; 18 | 19 | TEST_CASE("compression", "[config][compression]") { 20 | 21 | auto data = 22 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 23 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"_hexbytes; 24 | 25 | CHECK(data.size() == 81); 26 | 27 | auto d = data; 28 | session::config::compress_message(d, 1); 29 | 30 | CHECK(d[0] == 'z'); 31 | CHECK(d.size() == 18); 32 | CHECK(to_hex(d) == "7a28b52ffd205145000010aaaa01008c022c"); 33 | 34 | d = data; 35 | session::config::compress_message(d, 5); 36 | CHECK(d[0] == 'z'); 37 | CHECK(d.size() == 17); 38 | CHECK(to_hex(d) == "7a28b52ffd20513d000008aa01000dea84"); 39 | 40 | // This message (from the user profile test case) doesn't compress any better than plaintext 41 | // with zstd compression, so the compress_message call shouldn't change it. 42 | // clang-format off 43 | data = 44 | "d" 45 | "1:#" "i1e" 46 | "1:&" "d" 47 | "1:n" "6:Kallie" 48 | "1:p" "34:http://example.org/omg-pic-123.bmp" 49 | "1:q" "6:secret" 50 | "e" 51 | "1:<" "l" 52 | "l" 53 | "i0e" 54 | "32:"_bytes + 55 | "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965"_hexbytes + 56 | "de" 57 | "e" 58 | "e" 59 | "1:=" "d" 60 | "1:n" "0:" 61 | "1:p" "0:" 62 | "1:q" "0:" 63 | "e" 64 | "e"_bytes; 65 | // 66 | // If we add some more repetition in it, though, it will: 67 | auto data2 = 68 | "d" 69 | "1:#" "i1e" 70 | "1:&" "d" 71 | "1:n" "12:KallieKallie" 72 | "1:p" "29:http://kallie.example.org/Kallie.bmp" 73 | "1:q" "24:KallieKalliesecretKallie" 74 | "e" 75 | "1:<" "l" 76 | "l" 77 | "i0e" 78 | "32:"_bytes + 79 | "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965"_hexbytes + 80 | "de" 81 | "e" 82 | "e" 83 | "1:=" "d" 84 | "1:n" "0:" 85 | "1:p" "0:" 86 | "1:q" "0:" 87 | "e" 88 | "e"_bytes; 89 | // clang-format on 90 | 91 | d = data; 92 | intptr_t dptr = reinterpret_cast(d.data()); 93 | 94 | // Doesn't compress, so shouldn't change: 95 | CHECK(d.size() == 142); 96 | session::config::compress_message(d, 1); 97 | CHECK(d[0] == 'd'); 98 | CHECK(d.size() == 142); 99 | CHECK(reinterpret_cast(d.data()) == dptr); 100 | 101 | // Test some compression levels with exact compression values. (Note that this will change if 102 | // we change the version of external/zstd, but should be constant otherwise for any given 103 | // version of zstd). 104 | d = data2; 105 | session::config::compress_message(d, 1); 106 | CHECK(d[0] == 'z'); 107 | CHECK(d.size() == 161); 108 | CHECK(d.size() < data2.size()); 109 | CHECK(to_hex(d) == 110 | "7a28b52ffd20aabd0400640864313a23693165313a2664313a6e31323a4b616c6c6965313a7032393a68" 111 | "7474703a2f2f6b2e6578616d706c652e6f72672f4b626d70313a71323473656372657465313a3c6c6c69" 112 | "306533323aea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c9656465656531" 113 | "3a3d64313a6e303a313a70303a313a71303a656505003587f2d5e1c02836af9aa13401"); 114 | 115 | d = data2; 116 | session::config::compress_message(d, 5); 117 | CHECK(d[0] == 'z'); 118 | CHECK(d.size() == 156); 119 | CHECK(d.size() < data2.size()); 120 | CHECK(to_hex(d) == 121 | "7a28b52ffd20aa950400a40764313a23693165313a2664313a6e31323a4b616c6c6965313a7032393a68" 122 | "7474703a2f2f6b2e6578616d706c652e6f72672f4b626d70313a71323473656372657465313a3c6c6c69" 123 | "306533323aea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c96564653d303a" 124 | "313a7071303a65650800a8d0880966a9827e19e0572706a3d8bc6a86d204"); 125 | 126 | d = data2; 127 | session::config::compress_message(d, 19); 128 | CHECK(d[0] == 'z'); 129 | CHECK(d.size() == 157); // Yeah, it actually gets *bigger* with supposedly "higher" compression 130 | CHECK(d.size() < data2.size()); 131 | CHECK(to_hex(d) == 132 | "7a28b52ffd20aa9d0400e40764313a23693165313a2664313a6e31323a4b616c6c6965313a7032393a68" 133 | "7474703a2f2f6b2e6578616d706c652e6f72672f4b626d70313a71323473656372657465313a3c6c6c69" 134 | "306533323aea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c96564653d6431" 135 | "3a6e303a313a7071303a6565070028812c55282f03fceac460149b57cd509a"); 136 | } 137 | -------------------------------------------------------------------------------- /tests/test_curve25519.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "session/curve25519.h" 7 | #include "session/curve25519.hpp" 8 | #include "utils.hpp" 9 | 10 | TEST_CASE("X25519 key pair generation", "[curve25519][keypair]") { 11 | auto kp1 = session::curve25519::curve25519_key_pair(); 12 | auto kp2 = session::curve25519::curve25519_key_pair(); 13 | 14 | CHECK(kp1.first.size() == 32); 15 | CHECK(kp1.second.size() == 64); 16 | CHECK(kp1.first != kp2.first); 17 | CHECK(kp1.second != kp2.second); 18 | } 19 | 20 | TEST_CASE("X25519 conversion", "[curve25519][to curve25519 pubkey]") { 21 | using namespace session; 22 | 23 | auto ed_pk1 = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; 24 | auto ed_pk2 = "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"_hexbytes; 25 | 26 | auto x_pk1 = curve25519::to_curve25519_pubkey(to_unsigned_sv(ed_pk1)); 27 | auto x_pk2 = curve25519::to_curve25519_pubkey(to_unsigned_sv(ed_pk2)); 28 | 29 | CHECK(oxenc::to_hex(x_pk1.begin(), x_pk1.end()) == 30 | "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); 31 | CHECK(oxenc::to_hex(x_pk2.begin(), x_pk2.end()) == 32 | "aa654f00fc39fc69fd0db829410ca38177d7732a8d2f0934ab3872ac56d5aa74"); 33 | } 34 | 35 | TEST_CASE("X25519 conversion", "[curve25519][to curve25519 seckey]") { 36 | using namespace session; 37 | 38 | auto ed_sk1 = 39 | "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab78862834829a" 40 | "87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"_hexbytes; 41 | auto ed_sk2 = 42 | "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876cd83ca3d13a" 43 | "d8a954d5011aa7861abe3a29ac25b70c4ed5234aff74d34ef5786"_hexbytes; 44 | auto x_sk1 = curve25519::to_curve25519_seckey(to_unsigned_sv(ed_sk1)); 45 | auto x_sk2 = curve25519::to_curve25519_seckey(to_unsigned_sv(ed_sk2)); 46 | 47 | CHECK(oxenc::to_hex(x_sk1.begin(), x_sk1.end()) == 48 | "207e5d97e761300f96c10adc11efdd6d5c15188a9a7682ec05b30ca017e9b447"); 49 | CHECK(oxenc::to_hex(x_sk2.begin(), x_sk2.end()) == 50 | "904943eff27142a8e5cd37c84e2437c9979a560b044bf9a65a8d644b325fe56a"); 51 | } 52 | -------------------------------------------------------------------------------- /tests/test_ed25519.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "session/ed25519.h" 7 | #include "session/ed25519.hpp" 8 | #include "utils.hpp" 9 | 10 | TEST_CASE("Ed25519 key pair generation", "[ed25519][keypair]") { 11 | // Generate two random key pairs and make sure they don't match 12 | auto kp1 = session::ed25519::ed25519_key_pair(); 13 | auto kp2 = session::ed25519::ed25519_key_pair(); 14 | 15 | CHECK(kp1.first.size() == 32); 16 | CHECK(kp1.second.size() == 64); 17 | CHECK(kp1.first != kp2.first); 18 | CHECK(kp1.second != kp2.second); 19 | } 20 | 21 | TEST_CASE("Ed25519 key pair generation seed", "[ed25519][keypair]") { 22 | using namespace session; 23 | 24 | auto ed_seed1 = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; 25 | auto ed_seed2 = "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"_hexbytes; 26 | auto ed_seed_invalid = "010203040506070809"_hexbytes; 27 | 28 | auto kp1 = session::ed25519::ed25519_key_pair(to_unsigned_sv(ed_seed1)); 29 | auto kp2 = session::ed25519::ed25519_key_pair(to_unsigned_sv(ed_seed2)); 30 | CHECK_THROWS(session::ed25519::ed25519_key_pair(to_unsigned_sv(ed_seed_invalid))); 31 | 32 | CHECK(kp1.first.size() == 32); 33 | CHECK(kp1.second.size() == 64); 34 | CHECK(kp1.first != kp2.first); 35 | CHECK(kp1.second != kp2.second); 36 | CHECK(oxenc::to_hex(kp1.first.begin(), kp1.first.end()) == 37 | "8862834829a87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"); 38 | CHECK(oxenc::to_hex(kp2.first.begin(), kp2.first.end()) == 39 | "cd83ca3d13ad8a954d5011aa7861abe3a29ac25b70c4ed5234aff74d34ef5786"); 40 | 41 | auto kp_sk1 = 42 | "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab78862834829a" 43 | "87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"; 44 | auto kp_sk2 = 45 | "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876cd83ca3d13a" 46 | "d8a954d5011aa7861abe3a29ac25b70c4ed5234aff74d34ef5786"; 47 | CHECK(oxenc::to_hex(kp1.second.begin(), kp1.second.end()) == kp_sk1); 48 | CHECK(oxenc::to_hex(kp2.second.begin(), kp2.second.end()) == kp_sk2); 49 | } 50 | 51 | TEST_CASE("Ed25519 seed for private key", "[ed25519][seed]") { 52 | using namespace session; 53 | 54 | auto ed_sk1 = 55 | "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab78862834829a" 56 | "87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"_hexbytes; 57 | auto ed_sk2 = "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"_hexbytes; 58 | auto ed_sk_invalid = "010203040506070809"_hexbytes; 59 | 60 | auto seed1 = session::ed25519::seed_for_ed_privkey(to_unsigned_sv(ed_sk1)); 61 | auto seed2 = session::ed25519::seed_for_ed_privkey(to_unsigned_sv(ed_sk2)); 62 | CHECK_THROWS(session::ed25519::seed_for_ed_privkey(to_unsigned_sv(ed_sk_invalid))); 63 | 64 | CHECK(oxenc::to_hex(seed1.begin(), seed1.end()) == 65 | "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); 66 | CHECK(oxenc::to_hex(seed2.begin(), seed2.end()) == 67 | "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"); 68 | } 69 | 70 | TEST_CASE("Ed25519", "[ed25519][signature]") { 71 | using namespace session; 72 | 73 | auto ed_seed = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; 74 | auto ed_pk = "8862834829a87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"_hexbytes; 75 | auto ed_invalid = "010203040506070809"_hexbytes; 76 | 77 | auto sig1 = session::ed25519::sign(to_unsigned_sv(ed_seed), to_unsigned_sv("hello")); 78 | CHECK_THROWS(session::ed25519::sign(to_unsigned_sv(ed_invalid), to_unsigned_sv("hello"))); 79 | 80 | auto expected_sig_hex = 81 | "e03b6e87a53d83f202f2501e9b52193dbe4a64c6503f88244948dee53271" 82 | "85011574589aa7b59bc9757f9b9c31b7be9c9212b92ac7c81e029ee21c338ee12405"; 83 | CHECK(oxenc::to_hex(sig1.begin(), sig1.end()) == expected_sig_hex); 84 | 85 | CHECK(session::ed25519::verify(sig1, ed_pk, to_unsigned_sv("hello"))); 86 | CHECK_THROWS(session::ed25519::verify(ed_invalid, ed_pk, to_unsigned_sv("hello"))); 87 | CHECK_THROWS(session::ed25519::verify(ed_pk, ed_invalid, to_unsigned_sv("hello"))); 88 | } 89 | -------------------------------------------------------------------------------- /tests/test_encrypt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "utils.hpp" 13 | 14 | using namespace session; 15 | using namespace std::literals; 16 | using namespace oxenc::literals; 17 | 18 | TEST_CASE("config message encryption", "[config][encrypt]") { 19 | auto message1 = "some message 1"_bytes; 20 | auto key1 = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"_hexbytes; 21 | auto key2 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"_hexbytes; 22 | auto enc1 = config::encrypt(message1, key1, "test-suite1"); 23 | CHECK(oxenc::to_hex(enc1.begin(), enc1.end()) == 24 | "f14f242a26638f3305707d1035e734577f943cd7d28af58e32637e" 25 | "0966dcaf2f4860cb4d0f8ba7e09d29e31f5e4a18f65847287a54a0"); 26 | auto enc2 = config::encrypt(message1, key1, "test-suite2"); 27 | CHECK(to_hex(enc2) != to_hex(enc1)); 28 | auto enc3 = config::encrypt(message1, key2, "test-suite1"); 29 | CHECK(to_hex(enc3) != to_hex(enc1)); 30 | auto nonce = enc1.substr(enc1.size() - 24); 31 | auto nonce2 = enc2.substr(enc2.size() - 24); 32 | auto nonce3 = enc3.substr(enc3.size() - 24); 33 | CHECK(to_hex(nonce) == "af2f4860cb4d0f8ba7e09d29e31f5e4a18f65847287a54a0"); 34 | CHECK(to_hex(nonce2) == "277e639d36ba46470dfff509a68cb73d9a96386c51739bdd"); 35 | CHECK(to_hex(nonce3) == to_hex(nonce)); 36 | 37 | auto plain = config::decrypt(enc1, key1, "test-suite1"); 38 | CHECK(plain == message1); 39 | CHECK_THROWS_AS(config::decrypt(enc1, key1, "test-suite2"), config::decrypt_error); 40 | CHECK_THROWS_AS(config::decrypt(enc1, key2, "test-suite1"), config::decrypt_error); 41 | 42 | enc1[3] = '\x42'; 43 | CHECK_THROWS_AS(config::decrypt(enc1, key1, "test-suite1"), config::decrypt_error); 44 | } 45 | 46 | TEST_CASE("config message padding", "[config][padding]") { 47 | static_assert(config::padded_size(1, 0) == 256); 48 | static_assert(config::padded_size(1, 10) == 256 - 10); 49 | static_assert(config::padded_size(246, 10) == 256 - 10); 50 | static_assert(config::padded_size(247, 10) == 512 - 10); 51 | static_assert(config::padded_size(247, 10) == 512 - 10); 52 | static_assert(config::padded_size(247, 256) == 256); 53 | static_assert(config::padded_size(3839, 96) == 4000); 54 | static_assert(config::padded_size(1, 0) == 256); 55 | static_assert(config::padded_size(1, 10) == 256 - 10); 56 | static_assert(config::padded_size(246, 10) == 256 - 10); 57 | static_assert(config::padded_size(247, 10) == 512 - 10); 58 | static_assert(config::padded_size(247, 10) == 512 - 10); 59 | static_assert(config::padded_size(247, 256) == 256); 60 | static_assert(config::padded_size(3744, 96) == 3744); 61 | static_assert(config::padded_size(3745, 96) == 4000); 62 | static_assert(config::padded_size(4864, 0) == 4864); 63 | static_assert(config::padded_size(4865, 0) == 5_kiB); 64 | static_assert(config::padded_size(5_kiB + 1, 0) == 6_kiB); 65 | static_assert(config::padded_size(9_kiB, 0) == 9_kiB); 66 | static_assert(config::padded_size(9_kiB + 1, 0) == 10_kiB); 67 | static_assert(config::padded_size(10_kiB + 1, 0) == 11_kiB); 68 | static_assert(config::padded_size(20_kiB, 0) == 20_kiB); 69 | static_assert(config::padded_size(20_kiB + 1, 0) == 22_kiB); 70 | static_assert(config::padded_size(38_kiB, 0) == 38_kiB); 71 | static_assert(config::padded_size(38_kiB + 1, 0) == 40_kiB); 72 | static_assert(config::padded_size(40_kiB + 1, 0) == 45_kiB); 73 | static_assert(config::padded_size(45_kiB + 1, 0) == 50_kiB); 74 | static_assert(config::padded_size(70_kiB, 0) == 70_kiB); 75 | static_assert(config::padded_size(70_kiB + 1, 0) == 75_kiB); // Coincides with max message size 76 | static_assert(config::padded_size(75_kiB, 0) == 75_kiB); // Coincides with max message size 77 | static_assert( 78 | config::padded_size(75_kiB - 24, 24) == 79 | 75_kiB - 24); // Coincides with max message size 80 | CHECK(true); 81 | } 82 | -------------------------------------------------------------------------------- /tests/test_hash.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "session/hash.h" 4 | #include "session/hash.hpp" 5 | #include "utils.hpp" 6 | 7 | TEST_CASE("Hash generation", "[hash][hash]") { 8 | auto hash1 = session::hash::hash(32, to_usv("TestMessage"), std::nullopt); 9 | auto hash2 = session::hash::hash(32, to_usv("TestMessage"), std::nullopt); 10 | auto hash3 = session::hash::hash(32, to_usv("TestMessage"), to_usv("TestKey")); 11 | auto hash4 = session::hash::hash(32, to_usv("TestMessage"), to_usv("TestKey")); 12 | auto hash5 = session::hash::hash(64, to_usv("TestMessage"), std::nullopt); 13 | auto hash6 = session::hash::hash(64, to_usv("TestMessage"), to_usv("TestKey")); 14 | CHECK_THROWS(session::hash::hash(10, to_usv("TestMessage"), std::nullopt)); 15 | CHECK_THROWS(session::hash::hash(100, to_usv("TestMessage"), std::nullopt)); 16 | CHECK_THROWS(session::hash::hash( 17 | 32, 18 | to_usv("TestMessage"), 19 | to_usv("KeyThatIsTooLongKeyThatIsTooLongKeyThatIsTooLongKeyThatIsTooLongKeyThatIsTooLon" 20 | "g"))); 21 | 22 | CHECK(hash1.size() == 32); 23 | CHECK(hash2.size() == 32); 24 | CHECK(hash3.size() == 32); 25 | CHECK(hash4.size() == 32); 26 | CHECK(hash5.size() == 64); 27 | CHECK(hash6.size() == 64); 28 | CHECK(hash1 == hash2); 29 | CHECK(hash1 != hash3); 30 | CHECK(hash3 == hash4); 31 | CHECK(hash1 != hash5); 32 | CHECK(hash3 != hash6); 33 | CHECK(oxenc::to_hex(hash1) == 34 | "2a48a12262e4548afb97fe2b04a912a02297d451169ee7ef2d01a28ea20286ab"); 35 | CHECK(oxenc::to_hex(hash2) == 36 | "2a48a12262e4548afb97fe2b04a912a02297d451169ee7ef2d01a28ea20286ab"); 37 | CHECK(oxenc::to_hex(hash3) == 38 | "3d643e479b626bb2907476e32ccf7bdbd1ac3efa0da6e2c335255c48dcc216b6"); 39 | CHECK(oxenc::to_hex(hash4) == 40 | "3d643e479b626bb2907476e32ccf7bdbd1ac3efa0da6e2c335255c48dcc216b6"); 41 | 42 | auto expected_hash5 = 43 | "9d9085ac026fe3542abbeb2ea2ec05f5c37aecd7695f6cc41e9ccf39014196a39c02db69c44" 44 | "16d5c45acc2e9469b7f274992b2858f3bb2746becb48c8b56ce4b"; 45 | auto expected_hash6 = 46 | "6a2faad89cf9010a4270cba07cc96cfb36688106e080b15fef66bb03c68e877874c9059edf5" 47 | "3d03c1330b2655efdad6e4aa259118b6ea88698ea038efb9d52ce"; 48 | CHECK(oxenc::to_hex(hash5) == expected_hash5); 49 | CHECK(oxenc::to_hex(hash6) == expected_hash6); 50 | } 51 | -------------------------------------------------------------------------------- /tests/test_onionreq.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "utils.hpp" 6 | 7 | using namespace session; 8 | using namespace session::onionreq; 9 | 10 | TEST_CASE("Onion request encryption", "[encryption][onionreq]") { 11 | 12 | auto A = "bbdfc83022d0aff084a6f0c529a93d1c4d728bf7e41199afed0e01ae70d20540"_hexbytes; 13 | auto a = "ccc335912da8939e2b44816728a5a4773063efa82bf7ae2d42a0abfa2caa452d"_hexbytes; 14 | auto B = "caea52c5b0c316d85ffb53ea536826618b13dee40685f166f632653114526a78"_hexbytes; 15 | auto b = "8fcd8ad3a15c76f76f1c56dff0c529999f8c59b4acda79e05666e54d5727dca1"_hexbytes; 16 | 17 | auto enc_gcm = 18 | "1eb6ae1cd72f60999486365749bd5dc15cc0b6a2a44d7d063daa5e93722f0c025fd00306403b61" 19 | ""_hexbytes; 20 | auto enc_gcm_broken1 = 21 | "1eb6ae1cd72f60999486365759bd5dc15cc0b6a2a44d7d063daa5e93722f0c025fd00306403b61" 22 | ""_hexbytes; 23 | auto enc_gcm_broken2 = 24 | "1eb6ae1cd72f60999486365749bd5dc15cc0b6a2a44d7d063daa5e93722f0c025fd00306403b69" 25 | ""_hexbytes; 26 | auto enc_xchacha20 = 27 | "9e1a3abe60eff3ea5c23556cc7e225b6f94355315f7281f66ecf4dbb06e7899a52b863e03cde3b28" 28 | "7d1638d765db75de02b032"_hexbytes; 29 | auto enc_xchacha20_broken1 = 30 | "9e1a3abe60eff3ea5c23556cc7e225b6f94355315f7281f66ecf4dbb06e7899a52b863e03cde3b28" 31 | "7d1638d765db75de02b033"_hexbytes; 32 | auto enc_xchacha20_broken2 = 33 | "9e1a3abe60eff3ea5c23556ccfe225b6f94355315f7281f66ecf4dbb06e7899a52b863e03cde3b28" 34 | "7d1638d765db75de02b032"_hexbytes; 35 | 36 | HopEncryption e{x25519_seckey::from_bytes(b), x25519_pubkey::from_bytes(B), true}; 37 | 38 | CHECK(from_unsigned_sv(e.decrypt_aesgcm(enc_gcm, x25519_pubkey::from_bytes(A))) == 39 | "Hello world"); 40 | CHECK(from_unsigned_sv(e.decrypt_xchacha20(enc_xchacha20, x25519_pubkey::from_bytes(A))) == 41 | "Hello world"); 42 | CHECK_THROWS(e.decrypt_aesgcm(enc_xchacha20_broken1, x25519_pubkey::from_bytes(A))); 43 | CHECK_THROWS(e.decrypt_aesgcm(enc_xchacha20_broken2, x25519_pubkey::from_bytes(A))); 44 | CHECK_THROWS(e.decrypt_xchacha20(enc_xchacha20_broken1, x25519_pubkey::from_bytes(A))); 45 | CHECK_THROWS(e.decrypt_xchacha20(enc_xchacha20_broken2, x25519_pubkey::from_bytes(A))); 46 | } 47 | 48 | TEST_CASE("Onion request parser", "[onionreq][parser]") { 49 | 50 | auto A = "8167e97672005c669a48858c69895f395ca235219ac3f7a4210022b1f910e652"_hexbytes; 51 | auto a = "d2ee09e1a557a077d385fcb69a11ffb6909ecdcc8348def3e0e4172c8a1431c1"_hexbytes; 52 | auto B = "8388de69bc0d4b6196133233ad9a46ba0473474bc67718aad96a3a33c257f726"_hexbytes; 53 | auto b = "2f4d1c0d28e137777ec0a316e9f4f763e3e66662a6c51994c6315c9ef34b6deb"_hexbytes; 54 | 55 | auto enc_gcm = 56 | "270000009525d587d188c92a966eef0e7162bef99a6171a124575b998072a8ee7eb265e0b6f0930ed96504" 57 | "7b22656e635f74797065223a20226165732d67636d222c2022657068656d6572616c5f6b6579223a202238" 58 | "31363765393736373230303563363639613438383538633639383935663339356361323335323139616333" 59 | "6637613432313030323262316639313065363532227d"_hexbytes; 60 | auto enc_gcm_broken1 = ""_hexbytes; 61 | auto enc_gcm_broken2 = ""_hexbytes; 62 | auto enc_xchacha20 = 63 | "33000000e440bc244ddcafd947b86fc5a964aa58de54a6d75cc0f0f3840db14b6c1176a8e2e0a04d5fbdf9" 64 | "8f23adee1edc8362ab99b10b7b22656e635f74797065223a2022786368616368613230222c202265706865" 65 | "6d6572616c5f6b6579223a2022383136376539373637323030356336363961343838353863363938393566" 66 | "33393563613233353231396163336637613432313030323262316639313065363532227d"_hexbytes; 67 | auto enc_xchacha20_broken1 = ""_hexbytes; 68 | auto enc_xchacha20_broken2 = ""_hexbytes; 69 | 70 | OnionReqParser parser_gcm{B, b, enc_gcm}; 71 | CHECK(from_unsigned_sv(parser_gcm.payload()) == "Hello world"); 72 | CHECK(parser_gcm.remote_pubkey() == A); 73 | auto aes_reply = parser_gcm.encrypt_reply(to_unsigned_sv("Goodbye world")); 74 | CHECK(aes_reply.size() == 12 + 13 + 16); 75 | 76 | HopEncryption e{x25519_seckey::from_bytes(a), x25519_pubkey::from_bytes(A), false}; 77 | CHECK(from_unsigned_sv(e.decrypt_aesgcm(aes_reply, x25519_pubkey::from_bytes(B))) == 78 | "Goodbye world"); 79 | 80 | OnionReqParser parser_xchacha20{B, b, enc_xchacha20}; 81 | CHECK(from_unsigned_sv(parser_xchacha20.payload()) == "Hello world"); 82 | CHECK(parser_xchacha20.remote_pubkey() == A); 83 | auto xcha_reply = parser_xchacha20.encrypt_reply(to_unsigned_sv("Goodbye world")); 84 | CHECK(xcha_reply.size() == 16 + 13 + 24); 85 | CHECK(from_unsigned_sv(e.decrypt_xchacha20(xcha_reply, x25519_pubkey::from_bytes(B))) == 86 | "Goodbye world"); 87 | } 88 | -------------------------------------------------------------------------------- /tests/test_proto.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "utils.hpp" 10 | 11 | using namespace session::config; 12 | 13 | const std::vector groups{ 14 | Namespace::UserProfile, 15 | Namespace::Contacts, 16 | Namespace::ConvoInfoVolatile, 17 | Namespace::UserGroups}; 18 | 19 | const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; 20 | std::array ed_pk_raw; 21 | std::array ed_sk_raw; 22 | ustring_view load_seed() { 23 | crypto_sign_ed25519_seed_keypair(ed_pk_raw.data(), ed_sk_raw.data(), seed.data()); 24 | return {ed_sk_raw.data(), ed_sk_raw.size()}; 25 | } 26 | auto ed_sk = load_seed(); 27 | 28 | TEST_CASE("Protobuf Handling - Wrap, Unwrap", "[config][proto][wrap]") { 29 | auto msg = "Hello from the other side"_bytes; 30 | 31 | SECTION("Wrap/unwrap message types") { 32 | for (auto& n : groups) { 33 | auto shared_config_msg = protos::wrap_config(ed_sk, msg, 1, n); 34 | 35 | CHECK(not shared_config_msg.empty()); 36 | 37 | auto shared_config_parsed = protos::unwrap_config(ed_sk, shared_config_msg, n); 38 | // This will be false, as ::unwrap_config will return the parsed payload if it 39 | // successfully parses a protobuf wrapped message 40 | CHECK_FALSE(shared_config_msg == shared_config_parsed); 41 | // This will return true, as the parsed message will match the payload 42 | CHECK(shared_config_parsed == msg); 43 | } 44 | } 45 | 46 | SECTION("Message type payload comparison") { 47 | auto user_profile_msg = protos::wrap_config(ed_sk, msg, 1, Namespace::UserProfile); 48 | auto contacts_msg = protos::wrap_config(ed_sk, msg, 1, Namespace::Contacts); 49 | 50 | auto user_profile_parsed = 51 | protos::unwrap_config(ed_sk, user_profile_msg, Namespace::UserProfile); 52 | auto contacts_parsed = protos::unwrap_config(ed_sk, contacts_msg, Namespace::Contacts); 53 | 54 | // All of these will return true, as the parsed messages will be identical to the 55 | // payload, and therefore identical to one another 56 | CHECK(user_profile_parsed == contacts_parsed); 57 | CHECK(user_profile_parsed == msg); 58 | CHECK(contacts_parsed == msg); 59 | } 60 | } 61 | 62 | TEST_CASE("Protobuf Handling - Error Handling", "[config][proto][error]") { 63 | auto msg = "Hello from the other side"_bytes; 64 | auto addendum = "jfeejj0ifdoesam"_bytes; 65 | 66 | const auto user_profile_msg = protos::wrap_config(ed_sk, msg, 1, Namespace::UserProfile); 67 | const auto size = user_profile_msg.size(); 68 | 69 | // Testing three positions: front, inside the payload, and at the end 70 | const std::vector positions{0, size - 4, size}; 71 | 72 | for (auto& p : positions) { 73 | auto msg_copy = user_profile_msg; 74 | msg_copy.insert(p, addendum); 75 | 76 | REQUIRE_THROWS(protos::unwrap_config(ed_sk, msg_copy, Namespace::UserProfile)); 77 | } 78 | } 79 | 80 | TEST_CASE("Protobuf old config loading test", "[config][proto][old]") { 81 | 82 | const auto seed = "f887566576de6c16d9ec251d55e24c1400000000000000000000000000000000"_hexbytes; 83 | std::array ed_pk_raw; 84 | std::array ed_sk_raw; 85 | crypto_sign_ed25519_seed_keypair(ed_pk_raw.data(), ed_sk_raw.data(), seed.data()); 86 | ustring_view ed_sk{ed_sk_raw.data(), ed_sk_raw.size()}; 87 | 88 | auto old_conf = 89 | "080112c2060a03505554120f2f6170692f76312f6d6573736167651a9f060806120028e1c5a0beaf313801" 90 | "428f065228bb32b820169e0acb266f02efa007276be0668013a278fc9bfc111a40136f63de4206943c0509" 91 | "6155fa480cd0a7f5d27d6297166f5ed5c2a323ecdf7a754308dd385cdce81e7ed3a0a305577838105a0798" 92 | "dd92540f4b8eaa74f8c5720e0a394ce005444322354d6dfe1cb527520145f3794718e42730e15c97f7e45f" 93 | "b53f9f7d3918ee57e5c8462f80ae0d64792c261feb4b9ce06b18a10b3d8f7af8f791b1368bd4ae9bbe0036" 94 | "dc77f547c001e26c9c986269281bc3e8ef38c42ad2a02a9be517fc85c0c8fa4732f79138910f85bba0f898" 95 | "f278d8c2ed3e7d00cc5b4f1eb32ffc9572ec98fac529bec7ad8560dc06fc986516c00232e9618c372c0f57" 96 | "c19283e0424ec91864aad7277e22c085443cc0bfd39c0a83f0a1a8f856850ede7a751bd6206cb6683e462a" 97 | "033ad282e4947adbbe4973e823676ae0a72aa5f0f607f306fe82b91da9b7fe79d4fb4e8a45cb9ad5f20c15" 98 | "1a84073cc62d7ac794fdd2fe57bf49f1089f8644ad9f73d154d14c63d5ca7a07d1b6ab6b5846b2f4785fbf" 99 | "738de23c250a711f54c941fd6f5aac4417125bb2d0321cd9f1b97a31f310d4ea8149732276b8df9869fbc5" 100 | "412c9b7772961fab800a356155549ef54cefb9407d7f10b4323824aa8ea13facc79003b84dae3e5ef0db27" 101 | "5b056f4fbdf54f5f22e62291af8427fc17c3c1b3985f6ee149729d8a5b794b7e374f408eb8f36a76a89680" 102 | "e3c6106a9d5a82f6f04f5d8b603a97140b6469daac0ef32f84cc4ffc05f43c084591b10834b1d16d65ce14" 103 | "15dec77cb5851c338ccbb0d5ae2d2c1e5bc8ba0f59dfbc4575fd446c8486a1ac5370d5da8eb041f2ed560a" 104 | "bc1a6ad6ce6e00369ec5fd5eb0a35411ed24b36ecbf80f1dc6c18452c4b4bfc59131e04400df8986cac95c" 105 | "51bbd320ba901ff6110dad0c70442286cf6220a53c6f9693636a42d5523eeb1e5fb3453169581384fb8a8f" 106 | "3914fb6c01900a4f872f55742b117ddd7bd40c4c5911bb214e28eb9450dbdd0d831a93054c63f9a04bf50c" 107 | "db9aac0032c484062d7ba7bbe64e07bcd633eec8378d5d914732693c5e298f015ebde2ae45769ed319e267" 108 | "f0528f5cc6da268343b6647b20bae6e9ee8d92cca702"_hexbytes; 109 | 110 | CHECK_NOTHROW(protos::unwrap_config(ed_sk, old_conf, Namespace::UserProfile)); 111 | } 112 | -------------------------------------------------------------------------------- /tests/test_random.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "session/random.h" 4 | #include "session/random.hpp" 5 | #include "utils.hpp" 6 | 7 | TEST_CASE("Random generation", "[random][random]") { 8 | auto rand1 = session::random::random(10); 9 | auto rand2 = session::random::random(10); 10 | auto rand3 = session::random::random(20); 11 | 12 | CHECK(rand1.size() == 10); 13 | CHECK(rand2.size() == 10); 14 | CHECK(rand3.size() == 20); 15 | CHECK(rand1 != rand2); 16 | } 17 | -------------------------------------------------------------------------------- /tests/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "session/config/base.h" 14 | 15 | using ustring = std::basic_string; 16 | using ustring_view = std::basic_string_view; 17 | 18 | inline ustring operator""_bytes(const char* x, size_t n) { 19 | return {reinterpret_cast(x), n}; 20 | } 21 | inline ustring operator""_hexbytes(const char* x, size_t n) { 22 | ustring bytes; 23 | oxenc::from_hex(x, x + n, std::back_inserter(bytes)); 24 | return bytes; 25 | } 26 | 27 | inline std::string to_hex(ustring_view bytes) { 28 | std::string hex; 29 | oxenc::to_hex(bytes.begin(), bytes.end(), std::back_inserter(hex)); 30 | return hex; 31 | } 32 | 33 | inline constexpr auto operator""_kiB(unsigned long long kiB) { 34 | return kiB * 1024; 35 | } 36 | 37 | inline int64_t get_timestamp_ms() { 38 | return std::chrono::duration_cast( 39 | std::chrono::system_clock::now().time_since_epoch()) 40 | .count(); 41 | } 42 | 43 | inline std::string_view to_sv(ustring_view x) { 44 | return {reinterpret_cast(x.data()), x.size()}; 45 | } 46 | inline ustring_view to_usv(std::string_view x) { 47 | return {reinterpret_cast(x.data()), x.size()}; 48 | } 49 | template 50 | ustring_view to_usv(const std::array& data) { 51 | return {data.data(), N}; 52 | } 53 | 54 | inline std::string printable(ustring_view x) { 55 | std::string p; 56 | for (auto c : x) { 57 | if (c >= 0x20 && c <= 0x7e) 58 | p += c; 59 | else 60 | p += "\\x" + oxenc::to_hex(&c, &c + 1); 61 | } 62 | return p; 63 | } 64 | inline std::string printable(std::string_view x) { 65 | return printable(to_usv(x)); 66 | } 67 | std::string printable(const unsigned char* x) = delete; 68 | inline std::string printable(const unsigned char* x, size_t n) { 69 | return printable({x, n}); 70 | } 71 | 72 | template 73 | std::set as_set(const Container& c) { 74 | return {c.begin(), c.end()}; 75 | } 76 | 77 | template 78 | std::set> make_set(T&&... args) { 79 | return {std::forward(args)...}; 80 | } 81 | 82 | template 83 | std::vector> view_vec(std::vector>&& v) = delete; 84 | template 85 | std::vector> view_vec(const std::vector>& v) { 86 | std::vector> vv; 87 | vv.reserve(v.size()); 88 | std::copy(v.begin(), v.end(), std::back_inserter(vv)); 89 | return vv; 90 | } 91 | -------------------------------------------------------------------------------- /utils/android.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if ! [ -f LICENSE ] || ! [ -d include/session ]; then 6 | echo "You need to run this as ./utils/android.sh from the top-level libsession-util project directory" 7 | exit 1 8 | fi 9 | 10 | if [ -z "$NDK" ]; then 11 | echo "NDK environment variable needs to be set to the Android NDK path" >&2 12 | exit 1 13 | fi 14 | 15 | archive="${1:-libsession-util-android-TAG.tar.xz}" 16 | 17 | if [[ "$archive" =~ TAG ]]; then 18 | if [ -n "$DRONE_TAG" ]; then 19 | tag="$DRONE_TAG" 20 | elif [ -n "$DRONE_COMMIT" ]; then 21 | tag="$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}" 22 | else 23 | tag="$(date +%Y%m%dT%H%M%SZ)-$(git rev-parse --short=9 HEAD)" 24 | fi 25 | 26 | archive="${archive/TAG/$tag}" 27 | fi 28 | 29 | 30 | set -x 31 | 32 | abis=(armeabi-v7a arm64-v8a x86_64 x86) 33 | for abi in "${abis[@]}"; do 34 | build="build-android/$abi" 35 | echo "Building android $abi in $build" 36 | 37 | ./utils/static-bundle.sh "$build" "" \ 38 | -DCMAKE_TOOLCHAIN_FILE="$NDK/build/cmake/android.toolchain.cmake" \ 39 | -DANDROID_ABI=$abi \ 40 | -DANDROID_ARM_MODE=arm \ 41 | -DANDROID_PLATFORM=android-23 \ 42 | -DANDROID_STL=c++_static 43 | done 44 | 45 | cd build-android 46 | 47 | pkg="${archive%%.tar.xz}" 48 | 49 | mkdir -p "$pkg"/include 50 | cp -rv ../include/session "$pkg"/include/ 51 | mkdir -p "$pkg"/include/oxenc 52 | cp -v ../external/oxen-encoding/oxenc/*.h x86_64/external/oxen-encoding/oxenc/version.h "$pkg"/include/oxenc/ 53 | 54 | for abi in "${abis[@]}"; do 55 | mkdir -p "$pkg"/lib/$abi 56 | cp -v $abi/libsession-util.a "$pkg"/lib/$abi 57 | done 58 | 59 | tar cvJf "$archive" "$pkg" 60 | 61 | echo "Packaged everything up at build-android/$archive" 62 | -------------------------------------------------------------------------------- /utils/ci/drone-docs-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script used with Drone CI to upload docs 4 | 5 | set -o errexit 6 | 7 | if [ -z "$SSH_KEY" ]; then 8 | echo -e "\n\n\n\e[31;1mUnable to upload debs: SSH_KEY not set\e[0m" 9 | # Just warn but don't fail, so that this doesn't trigger a build failure for untrusted builds 10 | exit 0 11 | fi 12 | 13 | echo "$SSH_KEY" >~/ssh_key 14 | 15 | set -o xtrace # Don't start tracing until *after* we write the ssh key 16 | 17 | chmod 600 ~/ssh_key 18 | 19 | 20 | sftp -i ~/ssh_key -b - -o StrictHostKeyChecking=off apidocs@chianina.oxen.io <ssh_key 17 | 18 | set -o xtrace # Don't start tracing until *after* we write the ssh key 19 | 20 | chmod 600 ssh_key 21 | 22 | branch_or_tag=${DRONE_BRANCH:-${DRONE_TAG:-unknown}} 23 | 24 | upload_to="oxen.rocks/${DRONE_REPO// /_}/${branch_or_tag// /_}" 25 | 26 | shopt -s nullglob 27 | filename=(libsession-util-*.tar.xz libsession-util-*.zip) 28 | if [ ${#filename[@]} != 1 ]; then 29 | echo "Expected (exactly) one file to upload, found: ${filename[*]}" >&2 30 | exit 1 31 | fi 32 | 33 | # sftp doesn't have any equivalent to mkdir -p, so we have to split the above up into a chain of 34 | # -mkdir a/, -mkdir a/b/, -mkdir a/b/c/, ... commands. The leading `-` allows the command to fail 35 | # without error. 36 | upload_dirs=(${upload_to//\// }) 37 | mkdirs= 38 | dir_tmp="" 39 | for p in "${upload_dirs[@]}"; do 40 | dir_tmp="$dir_tmp$p/" 41 | mkdirs="$mkdirs 42 | -mkdir $dir_tmp" 43 | done 44 | 45 | sftp -i ssh_key -b - -o StrictHostKeyChecking=off drone@oxen.rocks </dev/null) 6 | if [ $? -ne 0 ]; then 7 | binary=$(command -v clang-format-mp-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null) 8 | fi 9 | if [ $? -ne 0 ]; then 10 | binary=$(command -v clang-format 2>/dev/null) 11 | if [ $? -ne 0 ]; then 12 | echo "Please install clang-format version $CLANG_FORMAT_DESIRED_VERSION and re-run this script." 13 | exit 1 14 | fi 15 | version=$(clang-format --version) 16 | if [[ ! $version == *"clang-format version $CLANG_FORMAT_DESIRED_VERSION"* ]]; then 17 | echo "Please install clang-format version $CLANG_FORMAT_DESIRED_VERSION and re-run this script." 18 | exit 1 19 | fi 20 | fi 21 | 22 | cd "$(dirname $0)/../" 23 | readarray -t sources < <(find include proto src tests | grep -E '\.([hc](pp)?)$' | grep -v '\#' | grep -v Catch2 | grep -v -E '\.pb\.(h|cc)$') 24 | if [ "$1" = "verify" ] ; then 25 | if [ $($binary --output-replacements-xml "${sources[@]}" | grep '' | wc -l) -ne 0 ] ; then 26 | exit 2 27 | fi 28 | else 29 | $binary -i "${sources[@]}" &> /dev/null 30 | fi 31 | 32 | jsonnet_format=$(command -v jsonnetfmt 2>/dev/null) 33 | if [ $? -eq 0 ]; then 34 | if [ "$1" = "verify" ]; then 35 | if ! $jsonnet_format --test .drone.jsonnet; then 36 | exit 4 37 | fi 38 | else 39 | $jsonnet_format --in-place .drone.jsonnet 40 | fi 41 | fi 42 | -------------------------------------------------------------------------------- /utils/ios.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if ! [ -f LICENSE ] || ! [ -d include/session ]; then 6 | echo "You need to run this as ./utils/ios.sh from the top-level libsession-util project directory" >&2 7 | exit 1 8 | fi 9 | 10 | if ! command -v xcodebuild; then 11 | echo "xcodebuild not found; are you on macOS with Xcode and Xcode command-line tools installed?" >&2 12 | exit 1 13 | fi 14 | 15 | # Import settings from XCode (defaulting values if not present) 16 | 17 | VALID_SIM_ARCHS=(arm64 x86_64) 18 | VALID_DEVICE_ARCHS=(arm64) 19 | VALID_SIM_ARCH_PLATFORMS=(SIMULATORARM64 SIMULATOR64) 20 | VALID_DEVICE_ARCH_PLATFORMS=(OS64) 21 | 22 | OUTPUT_DIR="${TARGET_BUILD_DIR:-build-ios}" 23 | IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:-13} 24 | ENABLE_BITCODE=${ENABLE_BITCODE:-OFF} 25 | SHOULD_ACHIVE=${2:-true} # Parameter 2 is a flag indicating whether we want to archive the result 26 | 27 | # We want to customise the env variable so can't just default the value 28 | if [ -z "${TARGET_TEMP_DIR}" ]; then 29 | BUILD_DIR="build-ios" 30 | elif [ "${#ARCHS[@]}" = 1 ]; then 31 | BUILD_DIR="${TARGET_TEMP_DIR}/../libSession-util" 32 | fi 33 | 34 | # Can't dafault an array in the same way as above 35 | if [ -z "${ARCHS}" ]; then 36 | ARCHS=(arm64 x86_64) 37 | elif [ "${#ARCHS[@]}" = 1 ]; then 38 | # The env value is probably a string, convert it to an array just in case 39 | read -ra ARCHS <<< "$ARCHS" 40 | fi 41 | 42 | projdir="$PWD" 43 | UNIQUE_NAME="" 44 | 45 | if [ $SHOULD_ACHIVE = true ]; then 46 | UNIQUE_NAME="${1:-libsession-util-ios-TAG}" 47 | 48 | if [[ "$UNIQUE_NAME" =~ TAG ]]; then 49 | if [ -n "$DRONE_TAG" ]; then 50 | tag="$DRONE_TAG" 51 | elif [ -n "$DRONE_COMMIT" ]; then 52 | tag="$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}" 53 | else 54 | tag="$(date +%Y%m%dT%H%M%SZ)-$(git rev-parse --short=9 HEAD)" 55 | fi 56 | 57 | UNIQUE_NAME="${UNIQUE_NAME/TAG/$tag}" 58 | fi 59 | 60 | OUTPUT_DIR="${OUTPUT_DIR}/${UNIQUE_NAME}" 61 | fi 62 | 63 | 64 | set -x 65 | 66 | 67 | # Generate the target architectures we want to build for 68 | TARGET_ARCHS=() 69 | TARGET_PLATFORMS=() 70 | TARGET_SIM_ARCHS=() 71 | TARGET_DEVICE_ARCHS=() 72 | 73 | if [ -z $PLATFORM_NAME ] || [ $PLATFORM_NAME = "iphonesimulator" ]; then 74 | for i in "${!VALID_SIM_ARCHS[@]}"; do 75 | ARCH="${VALID_SIM_ARCHS[$i]}" 76 | ARCH_PLATFORM="${VALID_SIM_ARCH_PLATFORMS[$i]}" 77 | 78 | if [[ " ${ARCHS[*]} " =~ " ${ARCH} " ]]; then 79 | TARGET_ARCHS+=("sim-${ARCH}") 80 | TARGET_PLATFORMS+=("${ARCH_PLATFORM}") 81 | TARGET_SIM_ARCHS+=("sim-${ARCH}") 82 | fi 83 | done 84 | fi 85 | 86 | if [ -z $PLATFORM_NAME ] || [ $PLATFORM_NAME = "iphoneos" ]; then 87 | for i in "${!VALID_DEVICE_ARCHS[@]}"; do 88 | ARCH="${VALID_DEVICE_ARCHS[$i]}" 89 | ARCH_PLATFORM="${VALID_DEVICE_ARCH_PLATFORMS[$i]}" 90 | 91 | if [[ " ${ARCHS[*]} " =~ " ${ARCH} " ]]; then 92 | TARGET_ARCHS+=("ios-${ARCH}") 93 | TARGET_PLATFORMS+=("${ARCH_PLATFORM}") 94 | TARGET_DEVICE_ARCHS+=("ios-${ARCH}") 95 | fi 96 | done 97 | fi 98 | 99 | # Build the individual architectures 100 | for i in "${!TARGET_ARCHS[@]}"; do 101 | build="${BUILD_DIR}/${TARGET_ARCHS[$i]}" 102 | platform="${TARGET_PLATFORMS[$i]}" 103 | echo "Building ${TARGET_ARCHS[$i]} for $platform in $build" 104 | 105 | ./utils/static-bundle.sh "$build" "" \ 106 | -DCMAKE_TOOLCHAIN_FILE="${projdir}/external/ios-cmake/ios.toolchain.cmake" \ 107 | -DPLATFORM=$platform \ 108 | -DDEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET \ 109 | -DENABLE_BITCODE=$ENABLE_BITCODE \ 110 | -DENABLE_ONIONREQ=OFF # Temporary until we figure out why ios builds hate gmp 111 | done 112 | 113 | # If needed combine simulator builds into a multi-arch lib 114 | if [ "${#TARGET_SIM_ARCHS[@]}" -eq "1" ]; then 115 | # Single device build 116 | mkdir -p "${BUILD_DIR}/sim" 117 | rm -rf "${BUILD_DIR}/sim/libsession-util.a" 118 | cp "${BUILD_DIR}/${TARGET_SIM_ARCHS[0]}/libsession-util.a" "${BUILD_DIR}/sim/libsession-util.a" 119 | elif [ "${#TARGET_SIM_ARCHS[@]}" -gt "1" ]; then 120 | # Combine multiple device builds into a multi-arch lib 121 | mkdir -p "${BUILD_DIR}/sim" 122 | lipo -create "${BUILD_DIR}"/sim-*/libsession-util.a -output "${BUILD_DIR}/sim/libsession-util.a" 123 | fi 124 | 125 | # If needed combine device builds into a multi-arch lib 126 | if [ "${#TARGET_DEVICE_ARCHS[@]}" -eq "1" ]; then 127 | # Single device build 128 | mkdir -p "${BUILD_DIR}/ios" 129 | rm -rf "${BUILD_DIR}/ios/libsession-util.a" 130 | cp "${BUILD_DIR}/${TARGET_DEVICE_ARCHS[0]}/libsession-util.a" "${BUILD_DIR}/ios/libsession-util.a" 131 | elif [ "${#TARGET_DEVICE_ARCHS[@]}" -gt "1" ]; then 132 | # Combine multiple device builds into a multi-arch lib 133 | mkdir -p "${BUILD_DIR}/ios" 134 | lipo -create "${BUILD_DIR}"/ios-*/libsession-util.a -output "${BUILD_DIR}/ios/libsession-util.a" 135 | fi 136 | 137 | 138 | # Create a '.xcframework' so XCode can deal with the different architectures 139 | rm -rf "${OUTPUT_DIR}/libsession-util.xcframework" 140 | 141 | if [ "${#TARGET_SIM_ARCHS}" -gt "0" ] && [ "${#TARGET_DEVICE_ARCHS}" -gt "0" ]; then 142 | xcodebuild -create-xcframework \ 143 | -library "${BUILD_DIR}/ios/libsession-util.a" \ 144 | -library "${BUILD_DIR}/sim/libsession-util.a" \ 145 | -output "${OUTPUT_DIR}/libsession-util.xcframework" 146 | elif [ "${#TARGET_DEVICE_ARCHS}" -gt "0" ]; then 147 | xcodebuild -create-xcframework \ 148 | -library "${BUILD_DIR}/ios/libsession-util.a" \ 149 | -output "${OUTPUT_DIR}/libsession-util.xcframework" 150 | else 151 | xcodebuild -create-xcframework \ 152 | -library "${BUILD_DIR}/sim/libsession-util.a" \ 153 | -output "${OUTPUT_DIR}/libsession-util.xcframework" 154 | fi 155 | 156 | # Copy the headers over 157 | cp -rv include/session "${OUTPUT_DIR}/libsession-util.xcframework" 158 | 159 | # The 'module.modulemap' is needed for XCode to be able to find the headers 160 | modmap="${OUTPUT_DIR}/libsession-util.xcframework/module.modulemap" 161 | echo "module SessionUtil {" >"$modmap" 162 | echo " module capi {" >>"$modmap" 163 | for x in $(cd include && find session -name '*.h'); do 164 | echo " header \"$x\"" >>"$modmap" 165 | done 166 | echo -e " export *\n }" >>"$modmap" 167 | if false; then 168 | # If we include the cpp headers like this then Xcode will try to load them as C headers (which 169 | # of course breaks) and doesn't provide any way to only load the ones you need (because this is 170 | # Apple land, why would anything useful be available?). So we include the headers in the 171 | # archive but can't let xcode discover them because it will do it wrong. 172 | echo -e "\n module cppapi {" >>"$modmap" 173 | for x in $(cd include && find session -name '*.hpp'); do 174 | echo " header \"$x\"" >>"$modmap" 175 | done 176 | echo -e " export *\n }" >>"$modmap" 177 | fi 178 | echo "}" >>"$modmap" 179 | 180 | if [ $SHOULD_ACHIVE = true ]; then 181 | (cd "${OUTPUT_DIR}/.." && tar cvJf "${UNIQUE_NAME}.tar.xz" "${UNIQUE_NAME}") 182 | fi 183 | 184 | echo "Packaged everything up at ${OUTPUT_DIR}/libsession-util.xcframework" 185 | -------------------------------------------------------------------------------- /utils/macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if ! [ -f LICENSE ] || ! [ -d include/session ]; then 6 | echo "You need to run this as ./utils/macos.sh from the top-level libsession-util project directory" 7 | exit 1 8 | fi 9 | 10 | if ! command -v xcodebuild; then 11 | echo "xcodebuild not found; are you on macOS with Xcode and Xcode command-line tools installed?" >&2 12 | exit 1 13 | fi 14 | 15 | archive="${1:-libsession-util-macos-TAG.tar.xz}" 16 | 17 | if [[ "$archive" =~ TAG ]]; then 18 | if [ -n "$DRONE_TAG" ]; then 19 | tag="$DRONE_TAG" 20 | elif [ -n "$DRONE_COMMIT" ]; then 21 | tag="$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}" 22 | else 23 | tag="$(date +%Y%m%dT%H%M%SZ)-$(git rev-parse --short=9 HEAD)" 24 | fi 25 | 26 | archive="${archive/TAG/$tag}" 27 | fi 28 | 29 | 30 | set -x 31 | 32 | for i in arm64 x86_64; do 33 | build="build-macos/$i" 34 | 35 | if [ "$(uname -m)" == "$i" ]; then 36 | echo "Building for macos ($i) in $build" 37 | ./utils/static-bundle.sh "$build" "" 38 | else 39 | echo "Cross-compiling for macos ($i) in $build" 40 | # The args here are a bit weird: 41 | # - CMAKE_SYSTEM_NAME is needed for make cmake realize it is cross-compiling (even though 42 | # it's setting it to the default value). See cmake issue #21885. 43 | # - CMAKE_OSX_ARCHITECTURES tells cmake what to build for but it doesn't set the above 44 | # because, well, yeah it does. See CMake issue #21885 again. 45 | # - The "16" in ARCH_TRIPLET (which we need to cross-compile static deps) corresponds to 46 | # macOS 10.12, but of course Apple doesn't publish that anywhere because you should be 47 | # using swift and the app store. 48 | ./utils/static-bundle.sh "$build" "" \ 49 | -DCMAKE_SYSTEM_NAME=Darwin \ 50 | -DARCH_TRIPLET="$i-apple-darwin16" \ 51 | -DCMAKE_OSX_ARCHITECTURES=$i 52 | fi 53 | done 54 | 55 | pkg="${archive%%.tar.xz}" 56 | pkg_dir="build-macos/$pkg" 57 | 58 | mkdir -p "$pkg_dir"/{lib,include} 59 | 60 | # Combine arch builds a multi-arch lib 61 | lipo -create build-macos/{arm64,x86_64}/libsession-util.a -output "$pkg_dir"/lib/libsession-util.a 62 | 63 | # Copy the headers over 64 | cp -rv include/session "$pkg_dir/include" 65 | 66 | (cd build-macos && tar cvJf "$archive" "$pkg") 67 | 68 | echo "Packaged everything up at build-macos/$archive" 69 | -------------------------------------------------------------------------------- /utils/static-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Make libsession-util amalgam builds. 4 | # 5 | # Requires at least two arguments: 6 | # - first arg is the build dir 7 | # - second arg is the archive filename ending with .tar.xz or .zip; *or* an empty string. If given 8 | # an archive filename then we package up the .a and headers into the archive (inside the build 9 | # dir); TAG in the archive name will be replaced with the tag or date+commit. If empty we just build but don't package anything. 10 | # - extra arguments are passed to the cmake invocation. 11 | # 12 | 13 | if ! [ -f LICENSE ] || ! [ -d include/session ]; then 14 | echo "You need to run this as ./utils/static-bundle.sh from the top-level libsession-util project directory" >&2 15 | exit 1 16 | fi 17 | 18 | if [ "$#" -lt 2 ]; then 19 | echo "Usage: $0 BUILDDIR {PACKAGE_NAME|\"\"} [...extra cmake args...]" >&2 20 | exit 1 21 | fi 22 | 23 | builddir="$1"; shift 24 | archive="$1"; shift 25 | 26 | if [[ "$archive" =~ TAG ]]; then 27 | if [ -n "$DRONE_TAG" ]; then 28 | tag="$DRONE_TAG" 29 | elif [ -n "$DRONE_COMMIT" ]; then 30 | tag="$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}" 31 | else 32 | tag="$(date +%Y%m%dT%H%M%SZ)-$(git rev-parse --short=9 HEAD)" 33 | fi 34 | archive="${archive/TAG/$tag}" 35 | fi 36 | 37 | 38 | zip= 39 | if [[ "$archive" =~ ^[^-/][^/]*\.tar\.xz$ ]]; then 40 | pkg="${archive%%.tar.xz}" 41 | elif [[ "$archive" =~ ^[^-][^/]*\.zip$ ]]; then 42 | pkg="${archive%%.zip}" 43 | zip=1 44 | elif [ -n "$archive" ]; then 45 | echo "Invalid archive name '$archive': require NAME.tar.xz, NAME.zip, or empty" >&2 46 | exit 1 47 | fi 48 | 49 | 50 | set -e 51 | set -x 52 | 53 | mkdir -p "$builddir" 54 | projdir="$PWD" 55 | cd "$builddir" 56 | 57 | cmake -G 'Unix Makefiles' \ 58 | -DSTATIC=ON \ 59 | -DSTATIC_BUNDLE=ON \ 60 | -DBUILD_SHARED_LIBS=OFF \ 61 | -DWITH_TESTS=OFF \ 62 | -DCMAKE_BUILD_TYPE=Release \ 63 | "$@" \ 64 | "$projdir" 65 | 66 | make -j${JOBS:-$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1)} VERBOSE=1 session-util 67 | 68 | if [ -z "$archive" ]; then 69 | exit 0 70 | fi 71 | 72 | mkdir -p "$pkg"/{lib,include} 73 | cp -v libsession-util.a "$pkg"/lib 74 | cp -rv "$projdir"/include/session "$pkg"/include 75 | mkdir -p "$pkg"/include/oxenc 76 | cp -v "$projdir"/external/oxen-encoding/oxenc/*.h external/oxen-encoding/oxenc/version.h "$pkg"/include/oxenc/ 77 | 78 | if [ -z "$zip" ]; then 79 | tar cvJf "$archive" "$pkg" 80 | else 81 | zip -rv "$archive" "$pkg" 82 | fi 83 | 84 | echo "Packaged everything up at $builddir/$archive" 85 | --------------------------------------------------------------------------------