diff options
Diffstat (limited to '')
130 files changed, 50702 insertions, 0 deletions
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt new file mode 100755 index 0000000..086ac57 --- /dev/null +++ b/src/lib/CMakeLists.txt @@ -0,0 +1,594 @@ +# Copyright (c) 2018-2020 Ribose Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +include(GNUInstallDirs) +include(GenerateExportHeader) + +# these could probably be optional but are currently not +find_package(BZip2 REQUIRED) +find_package(ZLIB REQUIRED) + +# required packages +find_package(JSON-C 0.11 REQUIRED) +if (CRYPTO_BACKEND_BOTAN) + find_package(Botan2 2.14.0 REQUIRED) +endif() +if (CRYPTO_BACKEND_OPENSSL) + include(FindOpenSSL) + find_package(OpenSSL 1.1.1 REQUIRED) + include(FindOpenSSLFeatures) + if("${OPENSSL_VERSION}" VERSION_GREATER_EQUAL "3.0.0") + set(CRYPTO_BACKEND_OPENSSL3 1) + endif() +endif() + +# generate a config.h +include(CheckIncludeFileCXX) +include(CheckCXXSymbolExists) +check_include_file_cxx(fcntl.h HAVE_FCNTL_H) +check_include_file_cxx(inttypes.h HAVE_INTTYPES_H) +check_include_file_cxx(limits.h HAVE_LIMITS_H) +check_include_file_cxx(stdint.h HAVE_STDINT_H) +check_include_file_cxx(string.h HAVE_STRING_H) +check_include_file_cxx(sys/cdefs.h HAVE_SYS_CDEFS_H) +check_include_file_cxx(sys/cdefs.h HAVE_SYS_MMAN_H) +check_include_file_cxx(sys/resource.h HAVE_SYS_RESOURCE_H) +check_include_file_cxx(sys/stat.h HAVE_SYS_STAT_H) +check_include_file_cxx(sys/types.h HAVE_SYS_TYPES_H) +check_include_file_cxx(sys/param.h HAVE_SYS_PARAM_H) +check_include_file_cxx(unistd.h HAVE_UNISTD_H) +check_include_file_cxx(sys/wait.h HAVE_SYS_WAIT_H) +check_cxx_symbol_exists(mkdtemp "stdlib.h;unistd.h" HAVE_MKDTEMP) +check_cxx_symbol_exists(mkstemp "stdlib.h;unistd.h" HAVE_MKSTEMP) +check_cxx_symbol_exists(realpath stdlib.h HAVE_REALPATH) +check_cxx_symbol_exists(O_BINARY fcntl.h HAVE_O_BINARY) +check_cxx_symbol_exists(_O_BINARY fcntl.h HAVE__O_BINARY) +check_cxx_symbol_exists(_tempnam stdio.h HAVE__TEMPNAM) +set(HAVE_ZLIB_H "${ZLIB_FOUND}") +set(HAVE_BZLIB_H "${BZIP2_FOUND}") +# generate a version.h +configure_file(version.h.in version.h) + +# Checks feature in CRYPTO_BACKEND and puts the result into variable whose name is stored in RESULT_VARNAME variable. +function(backend_has_feature FEATURE RESULT_VARNAME) + if (CRYPTO_BACKEND_LOWERCASE STREQUAL "botan") + check_cxx_symbol_exists("BOTAN_HAS_${FEATURE}" botan/build.h ${RESULT_VARNAME}) + else() + message(STATUS "Looking for OpenSSL feature ${FEATURE}") + OpenSSLHasFeature(${FEATURE} ${RESULT_VARNAME}) + if (${RESULT_VARNAME}) + message(STATUS "Looking for OpenSSL feature ${FEATURE} - found") + endif() + set(${RESULT_VARNAME} "${${RESULT_VARNAME}}" PARENT_SCOPE) + endif() +endfunction() + +function(resolve_feature_state RNP_FEATURE BACKEND_FEATURES) + if (NOT ${RNP_FEATURE}) # User has explicitly disabled this feature + return() + endif() + + string(TOLOWER "${${RNP_FEATURE}}" ${RNP_FEATURE}) + if (${RNP_FEATURE} STREQUAL "auto") + set(MESSAGE_TYPE "NOTICE") + set(OUTCOME "Disabling") + else() # User has explicitly enabled this feature + set(MESSAGE_TYPE "FATAL_ERROR") + set(OUTCOME "Aborting") + endif() + + foreach(feature ${BACKEND_FEATURES}) + backend_has_feature("${feature}" _has_${feature}) + if (NOT ${_has_${feature}}) + set(${RNP_FEATURE} Off CACHE STRING "Autodetected" FORCE) + message(${MESSAGE_TYPE} "${RNP_FEATURE} requires ${CRYPTO_BACKEND} feature which is missing: ${feature}. ${OUTCOME}.") + return() + endif() + endforeach() + set(${RNP_FEATURE} On CACHE STRING "Autodetected" FORCE) +endfunction() + +function(openssl_nope RNP_FEATURE REASON) + if (NOT ${RNP_FEATURE}) # User has explicitly disabled this feature + return() + endif() + + string(TOLOWER "${${RNP_FEATURE}}" ${RNP_FEATURE}) + if (${RNP_FEATURE} STREQUAL "auto") + set(MESSAGE_TYPE "NOTICE") + set(OUTCOME "Disabling") + else() # User has explicitly enabled this feature + set(MESSAGE_TYPE "FATAL_ERROR") + set(OUTCOME "Aborting") + endif() + + set(${RNP_FEATURE} Off CACHE STRING "Auto -> Off as no support" FORCE) + message(${MESSAGE_TYPE} "${RNP_FEATURE} doesn't work with OpenSSL backend (${REASON}). ${OUTCOME}.") +endfunction() + +if(CRYPTO_BACKEND_BOTAN) + # check botan's enabled features + set(CMAKE_REQUIRED_INCLUDES "${BOTAN2_INCLUDE_DIRS}") + set(_botan_required_features + # base + BIGINT FFI HEX_CODEC PGP_S2K + # symmetric ciphers + BLOCK_CIPHER AES CAMELLIA DES + # cipher modes + MODE_CBC MODE_CFB + # RNG + AUTO_RNG AUTO_SEEDING_RNG HMAC HMAC_DRBG + # hash + CRC24 HASH MD5 SHA1 SHA2_32 SHA2_64 SHA3 + # public-key core + DL_GROUP DL_PUBLIC_KEY_FAMILY ECC_GROUP ECC_PUBLIC_KEY_CRYPTO PUBLIC_KEY_CRYPTO + # public-key algs + CURVE_25519 DSA ECDH ECDSA ED25519 ELGAMAL RSA + # public-key operations etc + EME_PKCS1v15 EMSA_PKCS1 EMSA_RAW KDF_BASE RFC3394_KEYWRAP SP800_56A + ) + foreach(feature ${_botan_required_features}) + check_cxx_symbol_exists("BOTAN_HAS_${feature}" botan/build.h _botan_has_${feature}) + if (NOT _botan_has_${feature}) + message(FATAL_ERROR "A required botan feature is missing: ${feature}") + endif() + endforeach() + + resolve_feature_state(ENABLE_SM2 "SM2;SM3;SM4") + resolve_feature_state(ENABLE_AEAD "AEAD_EAX;AEAD_OCB") + resolve_feature_state(ENABLE_TWOFISH "TWOFISH") + resolve_feature_state(ENABLE_IDEA "IDEA") + # Botan supports Brainpool curves together with SECP via the ECC_GROUP define + resolve_feature_state(ENABLE_BLOWFISH "BLOWFISH") + resolve_feature_state(ENABLE_CAST5 "CAST_128") + resolve_feature_state(ENABLE_RIPEMD160 "RIPEMD_160") + set(CMAKE_REQUIRED_INCLUDES) +endif() +if(CRYPTO_BACKEND_OPENSSL) + # check OpenSSL features + set(_openssl_required_features + # symmetric ciphers + AES-128-ECB AES-192-ECB AES-256-ECB AES-128-CBC AES-192-CBC AES-256-CBC + AES-128-OCB AES-192-OCB AES-256-OCB + CAMELLIA-128-ECB CAMELLIA-192-ECB CAMELLIA-256-ECB + DES-EDE3 + # hashes + MD5 SHA1 SHA224 SHA256 SHA384 SHA512 SHA3-256 SHA3-512 + # curves + PRIME256V1 SECP384R1 SECP521R1 SECP256K1 + # public key + RSAENCRYPTION DSAENCRYPTION DHKEYAGREEMENT ID-ECPUBLICKEY X25519 ED25519 + ) + foreach(feature ${_openssl_required_features}) + message(STATUS "Looking for OpenSSL feature ${feature}") + OpenSSLHasFeature("${feature}" _openssl_has_${feature}) + if (NOT _openssl_has_${feature}) + message(FATAL_ERROR "A required OpenSSL feature is missing: ${feature}") + endif() + message(STATUS "Looking for OpenSSL feature ${feature} - found") + endforeach() + + resolve_feature_state(ENABLE_BRAINPOOL "BRAINPOOLP256R1;BRAINPOOLP384R1;BRAINPOOLP512R1") + resolve_feature_state(ENABLE_IDEA "IDEA-ECB;IDEA-CBC") + resolve_feature_state(ENABLE_BLOWFISH "BF-ECB") + resolve_feature_state(ENABLE_CAST5 "CAST5-ECB") + resolve_feature_state(ENABLE_RIPEMD160 "RIPEMD160") + resolve_feature_state(ENABLE_AEAD "AES-128-OCB;AES-192-OCB;AES-256-OCB") + openssl_nope(ENABLE_SM2 "it's on our roadmap, see https://github.com/rnpgp/rnp/issues/1877") + #resolve_feature_state(ENABLE_SM2 "SM2;SM3;SM4-ECB") + openssl_nope(ENABLE_TWOFISH "Twofish isn't and won't be supported by OpenSSL, see https://github.com/openssl/openssl/issues/2046") +endif() + +configure_file(config.h.in config.h) + +if(CRYPTO_BACKEND_OPENSSL) + set(CRYPTO_SOURCES + crypto/bn_ossl.cpp + crypto/dsa_ossl.cpp + crypto/ec_curves.cpp + crypto/ec_ossl.cpp + crypto/ecdh_utils.cpp + crypto/ecdh_ossl.cpp + crypto/ecdsa_ossl.cpp + crypto/eddsa_ossl.cpp + crypto/dl_ossl.cpp + crypto/elgamal_ossl.cpp + crypto/hash_common.cpp + crypto/hash_ossl.cpp + crypto/hash_crc24.cpp + crypto/mpi.cpp + crypto/rng_ossl.cpp + crypto/rsa_ossl.cpp + crypto/s2k.cpp + crypto/s2k_ossl.cpp + crypto/symmetric_ossl.cpp + crypto/signatures.cpp + crypto/mem_ossl.cpp + crypto/cipher.cpp + crypto/cipher_ossl.cpp + ) + if(ENABLE_SM2) + list(APPEND CRYPTO_SOURCES crypto/sm2_ossl.cpp) + endif() +elseif(CRYPTO_BACKEND_BOTAN) + set(CRYPTO_SOURCES + crypto/bn.cpp + crypto/dsa.cpp + crypto/ec_curves.cpp + crypto/ec.cpp + crypto/ecdh_utils.cpp + crypto/ecdh.cpp + crypto/ecdsa.cpp + crypto/eddsa.cpp + crypto/elgamal.cpp + crypto/hash_common.cpp + crypto/hash.cpp + crypto/mpi.cpp + crypto/rng.cpp + crypto/rsa.cpp + crypto/s2k.cpp + crypto/symmetric.cpp + crypto/signatures.cpp + crypto/mem.cpp + crypto/cipher.cpp + crypto/cipher_botan.cpp + ) + if(ENABLE_SM2) + list(APPEND CRYPTO_SOURCES crypto/sm2.cpp) + endif() +else() + message(FATAL_ERROR "Unknown crypto backend: ${CRYPTO_BACKEND}.") +endif() +list(APPEND CRYPTO_SOURCES crypto/backend_version.cpp) + +# sha11collisiondetection sources +list(APPEND CRYPTO_SOURCES crypto/hash_sha1cd.cpp crypto/sha1cd/sha1.c crypto/sha1cd/ubc_check.c) + +add_library(librnp-obj OBJECT + # librepgp + ../librepgp/stream-armor.cpp + ../librepgp/stream-common.cpp + ../librepgp/stream-ctx.cpp + ../librepgp/stream-dump.cpp + ../librepgp/stream-key.cpp + ../librepgp/stream-packet.cpp + ../librepgp/stream-parse.cpp + ../librepgp/stream-sig.cpp + ../librepgp/stream-write.cpp + + # librekey + ../librekey/key_store_g10.cpp + ../librekey/key_store_kbx.cpp + ../librekey/key_store_pgp.cpp + ../librekey/rnp_key_store.cpp + + # cryptography + ${CRYPTO_SOURCES} + + # other sources + sec_profile.cpp + crypto.cpp + fingerprint.cpp + generate-key.cpp + key-provider.cpp + logging.cpp + json-utils.cpp + utils.cpp + pass-provider.cpp + pgp-key.cpp + rnp.cpp +) + +get_target_property(_comp_options librnp-obj COMPILE_OPTIONS) +string(REGEX MATCH "\\-fsanitize=[a-z,]*undefined" _comp_sanitizers "${_comp_options}" "${CMAKE_C_FLAGS}") +if (ENABLE_SANITIZERS OR _comp_sanitizers) + # sha1cd attempts to use unaligned access for optimisations on intel CPUs + # CFLAGS is checked as sanitizers may be enabled without CMake var + set_source_files_properties(crypto/sha1cd/sha1.c + PROPERTIES COMPILE_DEFINITIONS "SHA1DC_FORCE_ALIGNED_ACCESS" + ) +endif() + +set_target_properties(librnp-obj PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(librnp-obj + PUBLIC + "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/src/lib>" + "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src/common>" + "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>" + "$<INSTALL_INTERFACE:include>" + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/src" +) +target_link_libraries(librnp-obj PRIVATE JSON-C::JSON-C) +if (CRYPTO_BACKEND_BOTAN) + target_link_libraries(librnp-obj PRIVATE Botan2::Botan2) +elseif (CRYPTO_BACKEND_OPENSSL) + target_link_libraries(librnp-obj PRIVATE OpenSSL::Crypto) +endif() + +target_link_libraries(librnp-obj PRIVATE sexp) + +set_target_properties(librnp-obj PROPERTIES CXX_VISIBILITY_PRESET hidden) +if (TARGET BZip2::BZip2) + target_link_libraries(librnp-obj PRIVATE BZip2::BZip2) +endif() +if (TARGET ZLIB::ZLIB) + target_link_libraries(librnp-obj PRIVATE ZLIB::ZLIB) +endif() +if (BUILD_SHARED_LIBS) + target_compile_definitions(librnp-obj PRIVATE librnp_EXPORTS) +else() + target_compile_definitions(librnp-obj PRIVATE RNP_STATIC) +endif() + +add_library(librnp $<TARGET_OBJECTS:librnp-obj> $<TARGET_OBJECTS:rnp-common>) +if (OpenSSL::applink) + target_link_libraries(librnp PRIVATE OpenSSL::applink) +endif(OpenSSL::applink) + +set_target_properties(librnp + PROPERTIES + VERSION "${RNP_VERSION}" + SOVERSION "${RNP_VERSION_MAJOR}" + OUTPUT_NAME "rnp" +) + +if (BUILD_SHARED_LIBS) + add_library(librnp-static STATIC $<TARGET_OBJECTS:librnp-obj> $<TARGET_OBJECTS:rnp-common>) + if (OpenSSL::applink) + target_link_libraries(librnp-static PRIVATE OpenSSL::applink) + endif(OpenSSL::applink) + if (WIN32) + set_target_properties(librnp-static PROPERTIES OUTPUT_NAME "rnp-static") + else (WIN32) +# On Unix like systems we will build/install/pack shared and static libraries librnp.so and librnp.a +# On Windows we will build/install/pack dynamic, import and static libraries rnp.dll, rnp.lib and rnp-static.lib + set_target_properties(librnp-static PROPERTIES OUTPUT_NAME "rnp") + endif (WIN32) + # Limit symbols export only to rnp_* functions. + if (APPLE) + # use -export_symbols_list on Apple OSs + target_link_options(librnp PRIVATE -Wl,-exported_symbols_list "${CMAKE_CURRENT_SOURCE_DIR}/librnp.symbols") + set_target_properties(librnp PROPERTIES LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/librnp.symbols") + elseif(NOT WIN32) + target_link_options(librnp PRIVATE "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/librnp.vsc") + set_target_properties(librnp PROPERTIES LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/librnp.vsc") + endif() +else() + add_library(librnp-static ALIAS librnp) +endif() + +foreach (prop LINK_LIBRARIES INTERFACE_LINK_LIBRARIES INCLUDE_DIRECTORIES INTERFACE_INCLUDE_DIRECTORIES) + get_target_property(val librnp-obj ${prop}) + if (BUILD_SHARED_LIBS) + set_property(TARGET librnp-static PROPERTY ${prop} ${val}) + list(REMOVE_ITEM val "$<LINK_ONLY:sexp>") + set_property(TARGET librnp PROPERTY ${prop} ${val}) + else() + set_property(TARGET librnp PROPERTY ${prop} ${val}) + endif() + +endforeach() + +generate_export_header(librnp + BASE_NAME rnp + EXPORT_MACRO_NAME RNP_API + EXPORT_FILE_NAME rnp/rnp_export.h + STATIC_DEFINE RNP_STATIC + INCLUDE_GUARD_NAME RNP_EXPORT +) + +# This has precedence and can cause some confusion when the binary +# dir one isn't actually being used. To be improved. +if (NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + file(REMOVE "${CMAKE_CURRENT_SOURCE_DIR}/config.h") + file(REMOVE "${CMAKE_CURRENT_SOURCE_DIR}/version.h") +endif() + +if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12.0") + set(namelink_component NAMELINK_COMPONENT development) +else() + set(namelink_component) +endif() + +# add these to the rnp-targets export +# On Unix like systems we will build/install/pack shared and static libraries librnp.so and librnp.a +# On Windows we will build/install/pack dynamic, import and static libraries rnp.dll, rnp.lib and rnp-static.lib + +# If a client application uses shared rnp library, sexp is statically linked to librnp.so +# If a client application uses static rnp library, it still needs libsexp.a + +if (BUILD_SHARED_LIBS) +# both static and shared libraries +install(TARGETS librnp + EXPORT rnp-targets + LIBRARY + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + COMPONENT runtime + ${namelink_component} + ARCHIVE + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + COMPONENT development + ) + + install(TARGETS librnp-static sexp + EXPORT rnp-targets + ARCHIVE + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + COMPONENT development + ) +else(BUILD_SHARED_LIBS) +# static libraries only +install(TARGETS librnp sexp + EXPORT rnp-targets + ARCHIVE + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + COMPONENT development +) +endif(BUILD_SHARED_LIBS) + +# install dll only for windows +if (WIN32) + install(TARGETS librnp + RUNTIME + DESTINATION "${CMAKE_INSTALL_BINDIR}" + COMPONENT runtime + ) +endif(WIN32) + +# install headers +install( + FILES + "${PROJECT_SOURCE_DIR}/include/rnp/rnp.h" + COMPONENT + development + DESTINATION + "${CMAKE_INSTALL_INCLUDEDIR}/rnp" + RENAME + rnp.h +) +install( + FILES + "${PROJECT_SOURCE_DIR}/include/rnp/rnp_err.h" + COMPONENT + development + DESTINATION + "${CMAKE_INSTALL_INCLUDEDIR}/rnp" + RENAME + rnp_err.h +) +install( + FILES + "${PROJECT_BINARY_DIR}/src/lib/rnp/rnp_export.h" + COMPONENT + development + DESTINATION + "${CMAKE_INSTALL_INCLUDEDIR}/rnp" + RENAME + rnp_export.h +) + +# .cmake installs +set(INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/rnp") + +install(EXPORT rnp-targets + FILE rnp-targets.cmake + NAMESPACE rnp:: + DESTINATION "${INSTALL_CMAKEDIR}" + COMPONENT development +) + +include(CMakePackageConfigHelpers) +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/rnp-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/rnp-config.cmake" + INSTALL_DESTINATION "${INSTALL_CMAKEDIR}" +) +write_basic_package_version_file(rnp-config-version.cmake + VERSION "${PROJECT_VERSION}" + COMPATIBILITY SameMajorVersion +) +install ( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/rnp-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/rnp-config-version.cmake" + DESTINATION "${INSTALL_CMAKEDIR}" + COMPONENT development +) + +function(get_linked_libs libsvar dirsvar tgt) + get_target_property(imported ${tgt} IMPORTED) + list(APPEND visited_targets ${tgt}) + if (imported) + get_target_property(linkedlibs ${tgt} INTERFACE_LINK_LIBRARIES) + endif() + set(libs) + foreach (lib ${linkedlibs}) + if (TARGET ${lib}) + list(FIND visited_targets ${lib} visited) + if ((${visited} EQUAL -1) AND (${CMAKE_SHARED_LIBRARY_PREFIX})) + # library + get_target_property(liblocation ${lib} LOCATION) + get_filename_component(linkedlib ${liblocation} NAME_WE) + string(REGEX REPLACE "^${CMAKE_SHARED_LIBRARY_PREFIX}" "" linkedlib ${linkedlib}) + get_linked_libs(linkedlibs libdirs ${lib}) + list(APPEND libs ${linkedlib} ${linkedlibs}) + # directory + get_filename_component(libdir ${liblocation} DIRECTORY) + list(FIND ${dirsvar} ${libdir} seendir) + if (${seendir} EQUAL -1) + list(APPEND ${dirsvar} ${libdir} ${libdirs}) + endif() + endif() + endif() + endforeach() + set(visited_targets ${visited_targets} PARENT_SCOPE) + set(${libsvar} ${libs} PARENT_SCOPE) + set(${dirsvar} ${${dirsvar}} PARENT_SCOPE) +endfunction() + +get_linked_libs(libs dirs librnp) +set(linkercmd) +foreach (dir ${dirs}) + string(APPEND linkercmd "-L${dir} ") +endforeach() +foreach (lib ${libs}) + string(APPEND linkercmd "-l${lib} ") +endforeach() +string(STRIP "${linkercmd}" linkercmd) +set(LIBRNP_PRIVATE_LIBS ${linkercmd}) + +# create a pkgconfig .pc too +find_package(PkgConfig) +if (PKG_CONFIG_FOUND) + get_target_property(LIBRNP_OUTPUT_NAME librnp OUTPUT_NAME) + + if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") + set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}") + else() + set(PKGCONFIG_LIBDIR "\${prefix}/${CMAKE_INSTALL_LIBDIR}") + endif() + if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") + set(PKGCONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}") + else() + set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") + endif() + + configure_file( + "${PROJECT_SOURCE_DIR}/cmake/librnp.pc.in" + "${PROJECT_BINARY_DIR}/librnp.pc" + @ONLY + ) + install( + FILES "${PROJECT_BINARY_DIR}/librnp.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" + COMPONENT development + ) +endif() + +# Build and install man page +if (ENABLE_DOC) + add_adoc_man("${CMAKE_CURRENT_SOURCE_DIR}/librnp.3.adoc" ${RNP_VERSION}) +endif() diff --git a/src/lib/config.h.in b/src/lib/config.h.in new file mode 100644 index 0000000..f8880d5 --- /dev/null +++ b/src/lib/config.h.in @@ -0,0 +1,72 @@ +/*- + * Copyright (c) 2018-2020 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define PACKAGE_STRING "rnp @RNP_VERSION_FULL@" +#define PACKAGE_BUGREPORT "@BUGREPORT_EMAIL@" + +#cmakedefine HAVE_BZLIB_H +#cmakedefine HAVE_ZLIB_H + +#cmakedefine HAVE_FCNTL_H +#cmakedefine HAVE_INTTYPES_H +#cmakedefine HAVE_LIMITS_H +#cmakedefine HAVE_STDINT_H +#cmakedefine HAVE_STRING_H +#cmakedefine HAVE_SYS_CDEFS_H +#cmakedefine HAVE_SYS_MMAN_H +#cmakedefine HAVE_SYS_RESOURCE_H +#cmakedefine HAVE_SYS_STAT_H +#cmakedefine HAVE_SYS_TYPES_H +#cmakedefine HAVE_UNISTD_H +#cmakedefine HAVE_SYS_WAIT_H +#cmakedefine HAVE_SYS_PARAM_H +#cmakedefine HAVE_MKDTEMP +#cmakedefine HAVE_MKSTEMP +#cmakedefine HAVE_REALPATH +#cmakedefine HAVE_O_BINARY +#cmakedefine HAVE__O_BINARY +#cmakedefine HAVE__TEMPNAM + +#cmakedefine CRYPTO_BACKEND_BOTAN +#cmakedefine CRYPTO_BACKEND_OPENSSL +#cmakedefine CRYPTO_BACKEND_OPENSSL3 + +#cmakedefine ENABLE_SM2 +#cmakedefine ENABLE_AEAD +#cmakedefine ENABLE_TWOFISH +#cmakedefine ENABLE_BRAINPOOL +#cmakedefine ENABLE_IDEA +#cmakedefine ENABLE_BLOWFISH +#cmakedefine ENABLE_CAST5 +#cmakedefine ENABLE_RIPEMD160 + +/* Macro _GLIBCXX_USE_CXX11_ABI was first introduced with GCC 5.0, which + * we assume to be bundled with a sane implementation of std::regex. */ +#if !defined(__GNUC__) || defined(_GLIBCXX_USE_CXX11_ABI) || \ + (defined(WIN32) && !defined(MSYS)) || \ + ((defined(__clang__) && (__clang_major__ >= 4)) ) +#define RNP_USE_STD_REGEX 1 +#endif diff --git a/src/lib/crypto.cpp b/src/lib/crypto.cpp new file mode 100644 index 0000000..2634604 --- /dev/null +++ b/src/lib/crypto.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#ifdef HAVE_SYS_CDEFS_H +#include <sys/cdefs.h> +#endif + +#if defined(__NetBSD__) +__COPYRIGHT("@(#) Copyright (c) 2009 The NetBSD Foundation, Inc. All rights reserved."); +__RCSID("$NetBSD: crypto.c,v 1.36 2014/02/17 07:39:19 agc Exp $"); +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <string.h> +#include <time.h> +#include <rnp/rnp_def.h> + +#include <librepgp/stream-packet.h> +#include <librepgp/stream-key.h> + +#include "types.h" +#include "crypto/common.h" +#include "crypto.h" +#include "fingerprint.h" +#include "pgp-key.h" +#include "utils.h" + +bool +pgp_generate_seckey(const rnp_keygen_crypto_params_t &crypto, + pgp_key_pkt_t & seckey, + bool primary) +{ + /* populate pgp key structure */ + seckey = {}; + seckey.version = PGP_V4; + seckey.creation_time = crypto.ctx->time(); + seckey.alg = crypto.key_alg; + seckey.material.alg = crypto.key_alg; + seckey.tag = primary ? PGP_PKT_SECRET_KEY : PGP_PKT_SECRET_SUBKEY; + + switch (seckey.alg) { + case PGP_PKA_RSA: + if (rsa_generate(&crypto.ctx->rng, &seckey.material.rsa, crypto.rsa.modulus_bit_len)) { + RNP_LOG("failed to generate RSA key"); + return false; + } + break; + case PGP_PKA_DSA: + if (dsa_generate(&crypto.ctx->rng, + &seckey.material.dsa, + crypto.dsa.p_bitlen, + crypto.dsa.q_bitlen)) { + RNP_LOG("failed to generate DSA key"); + return false; + } + break; + case PGP_PKA_EDDSA: + if (eddsa_generate(&crypto.ctx->rng, &seckey.material.ec)) { + RNP_LOG("failed to generate EDDSA key"); + return false; + } + break; + case PGP_PKA_ECDH: + if (!ecdh_set_params(&seckey.material.ec, crypto.ecc.curve)) { + RNP_LOG("Unsupported curve [ID=%d]", crypto.ecc.curve); + return false; + } + if (crypto.ecc.curve == PGP_CURVE_25519) { + if (x25519_generate(&crypto.ctx->rng, &seckey.material.ec)) { + RNP_LOG("failed to generate x25519 key"); + return false; + } + seckey.material.ec.curve = crypto.ecc.curve; + break; + } + [[fallthrough]]; + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + if (!curve_supported(crypto.ecc.curve)) { + RNP_LOG("EC generate: curve %d is not supported.", (int) crypto.ecc.curve); + return false; + } + if (ec_generate(&crypto.ctx->rng, &seckey.material.ec, seckey.alg, crypto.ecc.curve)) { + RNP_LOG("failed to generate EC key"); + return false; + } + seckey.material.ec.curve = crypto.ecc.curve; + break; + case PGP_PKA_ELGAMAL: + if (elgamal_generate( + &crypto.ctx->rng, &seckey.material.eg, crypto.elgamal.key_bitlen)) { + RNP_LOG("failed to generate ElGamal key"); + return false; + } + break; + default: + RNP_LOG("key generation not implemented for PK alg: %d", seckey.alg); + return false; + } + seckey.sec_protection.s2k.usage = PGP_S2KU_NONE; + seckey.material.secret = true; + seckey.material.validity.mark_valid(); + /* fill the sec_data/sec_len */ + if (encrypt_secret_key(&seckey, NULL, crypto.ctx->rng)) { + RNP_LOG("failed to fill sec_data"); + return false; + } + return true; +} + +bool +key_material_equal(const pgp_key_material_t *key1, const pgp_key_material_t *key2) +{ + if (key1->alg != key2->alg) { + return false; + } + + switch (key1->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return mpi_equal(&key1->rsa.n, &key2->rsa.n) && mpi_equal(&key1->rsa.e, &key2->rsa.e); + case PGP_PKA_DSA: + return mpi_equal(&key1->dsa.p, &key2->dsa.p) && + mpi_equal(&key1->dsa.q, &key2->dsa.q) && + mpi_equal(&key1->dsa.g, &key2->dsa.g) && mpi_equal(&key1->dsa.y, &key2->dsa.y); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return mpi_equal(&key1->eg.p, &key2->eg.p) && mpi_equal(&key1->eg.g, &key2->eg.g) && + mpi_equal(&key1->eg.y, &key2->eg.y); + case PGP_PKA_EDDSA: + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + return (key1->ec.curve == key2->ec.curve) && mpi_equal(&key1->ec.p, &key2->ec.p); + default: + RNP_LOG("unknown public key algorithm: %d", (int) key1->alg); + return false; + } +} + +rnp_result_t +validate_pgp_key_material(const pgp_key_material_t *material, rnp::RNG *rng) +{ +#ifdef FUZZERS_ENABLED + /* do not timeout on large keys during fuzzing */ + return RNP_SUCCESS; +#else + switch (material->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return rsa_validate_key(rng, &material->rsa, material->secret); + case PGP_PKA_DSA: + return dsa_validate_key(rng, &material->dsa, material->secret); + case PGP_PKA_EDDSA: + return eddsa_validate_key(rng, &material->ec, material->secret); + case PGP_PKA_ECDH: + if (!curve_supported(material->ec.curve)) { + /* allow to import key if curve is not supported */ + RNP_LOG("ECDH validate: curve %d is not supported.", (int) material->ec.curve); + return RNP_SUCCESS; + } + return ecdh_validate_key(rng, &material->ec, material->secret); + case PGP_PKA_ECDSA: + if (!curve_supported(material->ec.curve)) { + /* allow to import key if curve is not supported */ + RNP_LOG("ECDH validate: curve %d is not supported.", (int) material->ec.curve); + return RNP_SUCCESS; + } + return ecdsa_validate_key(rng, &material->ec, material->secret); + case PGP_PKA_SM2: +#if defined(ENABLE_SM2) + return sm2_validate_key(rng, &material->ec, material->secret); +#else + RNP_LOG("SM2 key validation is not available."); + return RNP_ERROR_NOT_IMPLEMENTED; +#endif + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return elgamal_validate_key(&material->eg, material->secret) ? RNP_SUCCESS : + RNP_ERROR_GENERIC; + default: + RNP_LOG("unknown public key algorithm: %d", (int) material->alg); + } + + return RNP_ERROR_BAD_PARAMETERS; +#endif +} diff --git a/src/lib/crypto.h b/src/lib/crypto.h new file mode 100644 index 0000000..320daf8 --- /dev/null +++ b/src/lib/crypto.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** \file + */ + +#ifndef CRYPTO_H_ +#define CRYPTO_H_ + +#include <limits.h> +#include "crypto/common.h" +#include <rekey/rnp_key_store.h> + +/* raw key generation */ +bool pgp_generate_seckey(const rnp_keygen_crypto_params_t ¶ms, + pgp_key_pkt_t & seckey, + bool primary); + +/** generate a new primary key + * + * @param desc keygen description + * @param merge_defaults true if you want defaults to be set for unset + * keygen description parameters. + * @param primary_sec pointer to store the generated secret key, must not be NULL + * @param primary_pub pointer to store the generated public key, must not be NULL + * @return true if successful, false otherwise. + **/ +bool pgp_generate_primary_key(rnp_keygen_primary_desc_t &desc, + bool merge_defaults, + pgp_key_t & primary_sec, + pgp_key_t & primary_pub, + pgp_key_store_format_t secformat); + +/** generate a new subkey + * + * @param desc keygen description + * @param merge_defaults true if you want defaults to be set for unset + * keygen description parameters. + * @param primary_sec pointer to the primary secret key that will own this + * subkey, must not be NULL + * @param primary_pub pointer to the primary public key that will own this + * subkey, must not be NULL + * @param subkey_sec pointer to store the generated secret key, must not be NULL + * @param subkey_pub pointer to store the generated public key, must not be NULL + * @param password_provider the password provider that will be used to + * decrypt the primary key, may be NULL if primary key is unlocked + * @return true if successful, false otherwise. + **/ +bool pgp_generate_subkey(rnp_keygen_subkey_desc_t & desc, + bool merge_defaults, + pgp_key_t & primary_sec, + pgp_key_t & primary_pub, + pgp_key_t & subkey_sec, + pgp_key_t & subkey_pub, + const pgp_password_provider_t &password_provider, + pgp_key_store_format_t secformat); + +/** + * @brief Check two key material for equality. Only public part is checked, so this can be + * called on public/secret key material + * + * @param key1 first key material + * @param key2 second key material + * @return true if both key materials are equal or false otherwise + */ +bool key_material_equal(const pgp_key_material_t *key1, const pgp_key_material_t *key2); + +rnp_result_t validate_pgp_key_material(const pgp_key_material_t *material, rnp::RNG *rng); + +#endif /* CRYPTO_H_ */ diff --git a/src/lib/crypto/backend_version.cpp b/src/lib/crypto/backend_version.cpp new file mode 100644 index 0000000..859b048 --- /dev/null +++ b/src/lib/crypto/backend_version.cpp @@ -0,0 +1,184 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "backend_version.h" +#include "logging.h" +#if defined(CRYPTO_BACKEND_BOTAN) +#include <botan/version.h> +#elif defined(CRYPTO_BACKEND_OPENSSL) +#include <openssl/opensslv.h> +#include <openssl/crypto.h> +#include "ossl_common.h" +#if defined(CRYPTO_BACKEND_OPENSSL3) +#include <openssl/provider.h> +#endif +#include <string.h> +#include "config.h" +#ifndef RNP_USE_STD_REGEX +#include <regex.h> +#else +#include <regex> +#endif +#endif +#include <cassert> + +namespace rnp { + +const char * +backend_string() +{ +#if defined(CRYPTO_BACKEND_BOTAN) + return "Botan"; +#elif defined(CRYPTO_BACKEND_OPENSSL) + return "OpenSSL"; +#else +#error "Unknown backend" +#endif +} + +const char * +backend_version() +{ +#if defined(CRYPTO_BACKEND_BOTAN) + return Botan::short_version_cstr(); +#elif defined(CRYPTO_BACKEND_OPENSSL) + /* Use regexp to retrieve version (second word) from version string + * like "OpenSSL 1.1.1l 24 Aug 2021" + * */ + static char version[32] = {}; + if (version[0]) { + return version; + } + const char *reg = "OpenSSL (([0-9]\\.[0-9]\\.[0-9])[a-z]*(-beta[0-9])*(-dev)*) "; +#ifndef RNP_USE_STD_REGEX + static regex_t r; + regmatch_t matches[5]; + const char * ver = OpenSSL_version(OPENSSL_VERSION); + + if (!strlen(version)) { + if (regcomp(&r, reg, REG_EXTENDED) != 0) { + RNP_LOG("failed to compile regexp"); + return "unknown"; + } + } + if (regexec(&r, ver, 5, matches, 0) != 0) { + return "unknown"; + } + assert(sizeof(version) > matches[1].rm_eo - matches[1].rm_so); + memcpy(version, ver + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so); + version[matches[1].rm_eo - matches[1].rm_so] = '\0'; +#else + static std::regex re(reg, std::regex_constants::extended); + std::smatch result; + std::string ver = OpenSSL_version(OPENSSL_VERSION); + if (!std::regex_search(ver, result, re)) { + return "unknown"; + } + assert(sizeof(version) > result[1].str().size()); + strncpy(version, result[1].str().c_str(), sizeof(version) - 1); +#endif + return version; +#else +#error "Unknown backend" +#endif +} + +#if defined(CRYPTO_BACKEND_OPENSSL3) + +#if defined(ENABLE_IDEA) || defined(ENABLE_CAST5) || defined(ENABLE_BLOWFISH) || \ + defined(ENABLE_RIPEMD160) +#define OPENSSL_LOAD_LEGACY +#endif + +typedef struct openssl3_state { +#if defined(OPENSSL_LOAD_LEGACY) + OSSL_PROVIDER *legacy; +#endif + OSSL_PROVIDER *def; +} openssl3_state; + +bool +backend_init(void **param) +{ + if (!param) { + return false; + } + + *param = NULL; + openssl3_state *state = (openssl3_state *) calloc(1, sizeof(openssl3_state)); + if (!state) { + RNP_LOG("Allocation failure."); + return false; + } + /* Load default crypto provider */ + state->def = OSSL_PROVIDER_load(NULL, "default"); + if (!state->def) { + RNP_LOG("Failed to load default crypto provider: %s", ossl_latest_err()); + free(state); + return false; + } + /* Load legacy crypto provider if needed */ +#if defined(OPENSSL_LOAD_LEGACY) + state->legacy = OSSL_PROVIDER_load(NULL, "legacy"); + if (!state->legacy) { + RNP_LOG("Failed to load legacy crypto provider: %s", ossl_latest_err()); + OSSL_PROVIDER_unload(state->def); + free(state); + return false; + } +#endif + *param = state; + return true; +} + +void +backend_finish(void *param) +{ + if (!param) { + return; + } + openssl3_state *state = (openssl3_state *) param; + OSSL_PROVIDER_unload(state->def); +#if defined(OPENSSL_LOAD_LEGACY) + OSSL_PROVIDER_unload(state->legacy); +#endif + free(state); +} +#else +bool +backend_init(void **param) +{ + return true; +} + +void +backend_finish(void *param) +{ + // Do nothing +} +#endif + +} // namespace rnp diff --git a/src/lib/crypto/backend_version.h b/src/lib/crypto/backend_version.h new file mode 100644 index 0000000..13c5269 --- /dev/null +++ b/src/lib/crypto/backend_version.h @@ -0,0 +1,44 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_BACKEND_VERSION_H_ +#define CRYPTO_BACKEND_VERSION_H_ + +#include "config.h" + +namespace rnp { + +const char *backend_string(); + +const char *backend_version(); + +bool backend_init(void **param); + +void backend_finish(void *param); + +} // namespace rnp + +#endif // CRYPTO_BACKEND_VERSION_H_ diff --git a/src/lib/crypto/bn.cpp b/src/lib/crypto/bn.cpp new file mode 100644 index 0000000..d5ae6b4 --- /dev/null +++ b/src/lib/crypto/bn.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2017-2021 Ribose Inc. + * Copyright (c) 2012 Alistair Crooks <agc@NetBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "bn.h" +#include <botan/ffi.h> +#include <stdlib.h> +#include <assert.h> +#include "utils.h" + +/* essentiually, these are just wrappers around the botan functions */ +/* usually the order of args changes */ +/* the bignum_t API tends to have more const poisoning */ +/* these wrappers also check the arguments passed for sanity */ + +/* store in unsigned [big endian] format */ +int +bn_bn2bin(const bignum_t *a, unsigned char *b) +{ + if (!a || !b) { + return -1; + } + return botan_mp_to_bin(a->mp, b); +} + +bignum_t * +mpi2bn(const pgp_mpi_t *val) +{ + assert(val); + if (!val) { + RNP_LOG("NULL val."); + return NULL; + } + bignum_t *res = bn_new(); + if (!res) { + return NULL; + } + if (botan_mp_from_bin(res->mp, val->mpi, val->len)) { + bn_free(res); + res = NULL; + } + return res; +} + +bool +bn2mpi(const bignum_t *bn, pgp_mpi_t *val) +{ + val->len = bn_num_bytes(*bn); + if (val->len > PGP_MPINT_SIZE) { + RNP_LOG("Too large MPI."); + val->len = 0; + return false; + } + return bn_bn2bin(bn, val->mpi) == 0; +} + +bignum_t * +bn_new(void) +{ + bignum_t *a = (bignum_t *) calloc(1, sizeof(*a)); + if (!a) { + return NULL; + } + botan_mp_init(&a->mp); + return a; +} + +void +bn_free(bignum_t *a) +{ + if (a) { + botan_mp_destroy(a->mp); + free(a); + } +} + +size_t +bn_num_bytes(const bignum_t &a) +{ + size_t bytes = 0; + if (botan_mp_num_bits(a.mp, &bytes)) { + RNP_LOG("botan_mp_num_bits failed."); + } + return BITS_TO_BYTES(bytes); +} diff --git a/src/lib/crypto/bn.h b/src/lib/crypto/bn.h new file mode 100644 index 0000000..26cc547 --- /dev/null +++ b/src/lib/crypto/bn.h @@ -0,0 +1,64 @@ +/*- + * Copyright (c) 2017-2021 Ribose Inc. + * Copyright (c) 2012 Alistair Crooks <agc@NetBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_BN_H_ +#define RNP_BN_H_ + +#include <stdio.h> +#include <stdint.h> +#include "config.h" +#include "mpi.h" + +#if defined(CRYPTO_BACKEND_OPENSSL) +#include <openssl/bn.h> + +#define bignum_t BIGNUM +#elif defined(CRYPTO_BACKEND_BOTAN) +typedef struct botan_mp_struct *botan_mp_t; +typedef struct bignum_t_st { + botan_mp_t mp; +} bignum_t; + +#define BN_HANDLE(x) ((x).mp) +#define BN_HANDLE_PTR(x) ((x)->mp) +#else +#error "Unknown crypto backend." +#endif + +/*********************************/ + +bignum_t *bn_new(void); +void bn_free(bignum_t * /*a*/); + +int bn_bn2bin(const bignum_t * /*a*/, unsigned char * /*b*/); + +bignum_t *mpi2bn(const pgp_mpi_t *val); + +bool bn2mpi(const bignum_t *bn, pgp_mpi_t *val); + +size_t bn_num_bytes(const bignum_t &a); + +#endif diff --git a/src/lib/crypto/bn_ossl.cpp b/src/lib/crypto/bn_ossl.cpp new file mode 100644 index 0000000..34e1a3e --- /dev/null +++ b/src/lib/crypto/bn_ossl.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <assert.h> +#include "bn.h" +#include "logging.h" + +/* store in unsigned [big endian] format */ +int +bn_bn2bin(const bignum_t *a, unsigned char *b) +{ + if (!a || !b) { + return -1; + } + return BN_bn2bin(a, b) >= 0 ? 0 : -1; +} + +bignum_t * +mpi2bn(const pgp_mpi_t *val) +{ + assert(val); + if (!val) { + RNP_LOG("NULL val."); + return NULL; + } + bignum_t *res = bn_new(); + if (!res) { + return NULL; + } + if (!BN_bin2bn(val->mpi, val->len, res)) { + bn_free(res); + res = NULL; + } + return res; +} + +bool +bn2mpi(const bignum_t *bn, pgp_mpi_t *val) +{ + val->len = bn_num_bytes(*bn); + return bn_bn2bin(bn, val->mpi) == 0; +} + +bignum_t * +bn_new(void) +{ + return BN_new(); +} + +void +bn_free(bignum_t *a) +{ + BN_clear_free(a); +} + +size_t +bn_num_bytes(const bignum_t &a) +{ + return (BN_num_bits(&a) + 7) / 8; +} diff --git a/src/lib/crypto/cipher.cpp b/src/lib/crypto/cipher.cpp new file mode 100644 index 0000000..a6c6fce --- /dev/null +++ b/src/lib/crypto/cipher.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "config.h" +#include "symmetric.h" +#include "cipher.hpp" + +#if defined(CRYPTO_BACKEND_OPENSSL) +#include "cipher_ossl.hpp" +#elif defined(CRYPTO_BACKEND_BOTAN) +#include "cipher_botan.hpp" +#endif + +std::unique_ptr<Cipher> +Cipher::encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ +#if defined(CRYPTO_BACKEND_OPENSSL) + return Cipher_OpenSSL::encryption(cipher, mode, tag_size, disable_padding); +#elif defined(CRYPTO_BACKEND_BOTAN) + return Cipher_Botan::encryption(cipher, mode, tag_size, disable_padding); +#else +#error "Crypto backend not specified" +#endif +} + +std::unique_ptr<Cipher> +Cipher::decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ +#if defined(CRYPTO_BACKEND_OPENSSL) + return Cipher_OpenSSL::decryption(cipher, mode, tag_size, disable_padding); +#elif defined(CRYPTO_BACKEND_BOTAN) + return Cipher_Botan::decryption(cipher, mode, tag_size, disable_padding); +#else +#error "Crypto backend not specified" +#endif +} + +Cipher::Cipher(pgp_symm_alg_t alg) : m_alg(alg) +{ +} + +Cipher::~Cipher() +{ +} + +size_t +Cipher::block_size() const +{ + return pgp_block_size(m_alg); +} diff --git a/src/lib/crypto/cipher.hpp b/src/lib/crypto/cipher.hpp new file mode 100644 index 0000000..c9edf15 --- /dev/null +++ b/src/lib/crypto/cipher.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_CIPHER_HPP +#define RNP_CIPHER_HPP + +#include <memory> +#include <string> +#include <repgp/repgp_def.h> + +// Note: for AEAD modes we append the authentication tag to the ciphertext as in RFC 5116 +class Cipher { + public: + // the tag size should be 0 for non-AEAD and must be non-zero for AEAD modes (no default) + static std::unique_ptr<Cipher> encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size = 0, + bool disable_padding = false); + static std::unique_ptr<Cipher> decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size = 0, + bool disable_padding = false); + + virtual bool set_key(const uint8_t *key, size_t key_length) = 0; + virtual bool set_iv(const uint8_t *iv, size_t iv_length) = 0; + // only valid for AEAD modes + virtual bool set_ad(const uint8_t *ad, size_t ad_length) = 0; + + virtual size_t block_size() const; + virtual size_t update_granularity() const = 0; + + // input_length must be a multiple of update_granularity + virtual bool update(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) = 0; + // process final block and perform any padding + virtual bool finish(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) = 0; + + virtual ~Cipher(); + + protected: + Cipher(pgp_symm_alg_t alg); + + pgp_symm_alg_t m_alg; +}; + +#endif diff --git a/src/lib/crypto/cipher_botan.cpp b/src/lib/crypto/cipher_botan.cpp new file mode 100644 index 0000000..c2c4ab3 --- /dev/null +++ b/src/lib/crypto/cipher_botan.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include <sstream> +#include <cassert> +#include <botan/aead.h> +#include "cipher_botan.hpp" +#include "utils.h" +#include "types.h" + +static const id_str_pair cipher_mode_map[] = { + {PGP_CIPHER_MODE_CBC, "CBC"}, + {PGP_CIPHER_MODE_OCB, "OCB"}, + {0, NULL}, +}; + +static const id_str_pair cipher_map[] = { + {PGP_SA_AES_128, "AES-128"}, + {PGP_SA_AES_256, "AES-256"}, + {PGP_SA_IDEA, "IDEA"}, + {0, NULL}, +}; + +Cipher_Botan * +Cipher_Botan::create(pgp_symm_alg_t alg, const std::string &name, bool encrypt) +{ +#if !defined(ENABLE_IDEA) + if (alg == PGP_SA_IDEA) { + RNP_LOG("IDEA support has been disabled"); + return nullptr; + } +#endif +#if !defined(ENABLE_BLOWFISH) + if (alg == PGP_SA_BLOWFISH) { + RNP_LOG("Blowfish support has been disabled"); + return nullptr; + } +#endif +#if !defined(ENABLE_CAST5) + if (alg == PGP_SA_CAST5) { + RNP_LOG("CAST5 support has been disabled"); + return nullptr; + } +#endif + auto cipher = Botan::Cipher_Mode::create( + name, encrypt ? Botan::Cipher_Dir::ENCRYPTION : Botan::Cipher_Dir::DECRYPTION); + if (!cipher) { + RNP_LOG("Failed to create cipher '%s'", name.c_str()); + return nullptr; + } + return new (std::nothrow) Cipher_Botan(alg, std::move(cipher)); +} + +static std::string +make_name(pgp_symm_alg_t cipher, pgp_cipher_mode_t mode, size_t tag_size, bool disable_padding) +{ + const char *cipher_string = id_str_pair::lookup(cipher_map, cipher, NULL); + const char *mode_string = id_str_pair::lookup(cipher_mode_map, mode, NULL); + if (!cipher_string || !mode_string) { + return ""; + } + try { + std::stringstream ss; + ss << cipher_string << "/" << mode_string; + if (tag_size) { + ss << "(" << tag_size << ")"; + } + if (mode == PGP_CIPHER_MODE_CBC && disable_padding) { + ss << "/NoPadding"; + } + return ss.str(); + } catch (const std::exception &e) { + return ""; + } +} + +std::unique_ptr<Cipher_Botan> +Cipher_Botan::encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ + return std::unique_ptr<Cipher_Botan>( + create(cipher, make_name(cipher, mode, tag_size, disable_padding), true)); +} + +std::unique_ptr<Cipher_Botan> +Cipher_Botan::decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ + return std::unique_ptr<Cipher_Botan>( + create(cipher, make_name(cipher, mode, tag_size, disable_padding), false)); +} + +size_t +Cipher_Botan::update_granularity() const +{ + return m_cipher->update_granularity(); +} + +bool +Cipher_Botan::set_key(const uint8_t *key, size_t key_length) +{ + try { + m_cipher->set_key(key, key_length); + } catch (const std::exception &e) { + RNP_LOG("Failed to set key: %s", e.what()); + return false; + } + return true; +} + +bool +Cipher_Botan::set_iv(const uint8_t *iv, size_t iv_length) +{ + try { + m_cipher->start(iv, iv_length); + m_buf.reserve(this->update_granularity()); + } catch (const std::exception &e) { + RNP_LOG("Failed to set IV: %s", e.what()); + return false; + } + return true; +} + +bool +Cipher_Botan::set_ad(const uint8_t *ad, size_t ad_length) +{ + assert(m_cipher->authenticated()); + try { + dynamic_cast<Botan::AEAD_Mode &>(*m_cipher).set_associated_data(ad, ad_length); + } catch (const std::exception &e) { + RNP_LOG("Failed to set AAD: %s", e.what()); + return false; + } + return true; +} + +bool +Cipher_Botan::update(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) +{ + try { + size_t ud = this->update_granularity(); + m_buf.resize(ud); + + *input_consumed = 0; + *output_written = 0; + while (input_length >= ud && output_length >= ud) { + m_buf.assign(input, input + ud); + size_t written = m_cipher->process(m_buf.data(), ud); + std::copy(m_buf.data(), m_buf.data() + written, output); + input += ud; + output += written; + input_length -= ud; + output_length -= written; + + *output_written += written; + *input_consumed += ud; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + return true; +} + +bool +Cipher_Botan::finish(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) +{ + try { + *input_consumed = 0; + *output_written = 0; + size_t ud = this->update_granularity(); + if (input_length > ud) { + if (!update(output, + output_length, + output_written, + input, + input_length - ud, + input_consumed)) { + return false; + } + input += *input_consumed; + input_length = input_length - *input_consumed; + output += *output_written; + output_length -= *output_written; + } + Botan::secure_vector<uint8_t> final_block(input, input + input_length); + m_cipher->finish(final_block); + if (final_block.size() > output_length) { + RNP_LOG("Insufficient buffer"); + return false; + } + std::copy(final_block.begin(), final_block.end(), output); + *output_written += final_block.size(); + *input_consumed += input_length; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + return true; +} + +Cipher_Botan::Cipher_Botan(pgp_symm_alg_t alg, std::unique_ptr<Botan::Cipher_Mode> cipher) + : Cipher(alg), m_cipher(std::move(cipher)) +{ +} + +Cipher_Botan::~Cipher_Botan() +{ +} diff --git a/src/lib/crypto/cipher_botan.hpp b/src/lib/crypto/cipher_botan.hpp new file mode 100644 index 0000000..517d38b --- /dev/null +++ b/src/lib/crypto/cipher_botan.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_CIPHER_BOTAN_HPP +#define RNP_CIPHER_BOTAN_HPP + +#include "cipher.hpp" +#include <botan/cipher_mode.h> +#include <repgp/repgp_def.h> + +class Cipher_Botan : public Cipher { + public: + static std::unique_ptr<Cipher_Botan> encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding); + static std::unique_ptr<Cipher_Botan> decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding); + + bool set_key(const uint8_t *key, size_t key_length) override; + bool set_iv(const uint8_t *iv, size_t iv_length) override; + bool set_ad(const uint8_t *ad, size_t ad_length) override; + + size_t update_granularity() const override; + + bool update(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) override; + bool finish(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) override; + virtual ~Cipher_Botan(); + + private: + Cipher_Botan(pgp_symm_alg_t alg, std::unique_ptr<Botan::Cipher_Mode> cipher); + + std::unique_ptr<Botan::Cipher_Mode> m_cipher; + std::vector<uint8_t> m_buf; + + static Cipher_Botan *create(pgp_symm_alg_t alg, const std::string &name, bool encrypt); +}; + +#endif diff --git a/src/lib/crypto/cipher_ossl.cpp b/src/lib/crypto/cipher_ossl.cpp new file mode 100644 index 0000000..bb81d48 --- /dev/null +++ b/src/lib/crypto/cipher_ossl.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include <cassert> +#include <algorithm> + +#include "cipher_ossl.hpp" +#include "utils.h" +#include "types.h" +#include <openssl/err.h> + +static const id_str_pair cipher_mode_map[] = { + {PGP_CIPHER_MODE_CBC, "CBC"}, + {PGP_CIPHER_MODE_OCB, "OCB"}, + {0, NULL}, +}; + +static const id_str_pair cipher_map[] = { + {PGP_SA_AES_128, "AES-128"}, + {PGP_SA_AES_256, "AES-256"}, + {PGP_SA_IDEA, "IDEA"}, + {0, NULL}, +}; + +EVP_CIPHER_CTX * +Cipher_OpenSSL::create(pgp_symm_alg_t alg, + const std::string &name, + bool encrypt, + size_t tag_size, + bool disable_padding) +{ +#if !defined(ENABLE_IDEA) + if (alg == PGP_SA_IDEA) { + RNP_LOG("IDEA support has been disabled"); + return nullptr; + } +#endif +#if !defined(ENABLE_BLOWFISH) + if (alg == PGP_SA_BLOWFISH) { + RNP_LOG("Blowfish support has been disabled"); + return nullptr; + } +#endif +#if !defined(ENABLE_CAST5) + if (alg == PGP_SA_CAST5) { + RNP_LOG("CAST5 support has been disabled"); + return nullptr; + } +#endif + const EVP_CIPHER *cipher = EVP_get_cipherbyname(name.c_str()); + if (!cipher) { + RNP_LOG("Unsupported cipher: %s", name.c_str()); + return nullptr; + } + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + RNP_LOG("Failed to create cipher context: %lu", ERR_peek_last_error()); + return nullptr; + } + if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encrypt ? 1 : 0) != 1) { + RNP_LOG("Failed to initialize cipher: %lu", ERR_peek_last_error()); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + // set tag size + if (encrypt && tag_size) { + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_size, NULL) != 1) { + RNP_LOG("Failed to set AEAD tag length: %lu", ERR_peek_last_error()); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + } + if (disable_padding) { + EVP_CIPHER_CTX_set_padding(ctx, 0); + } + return ctx; +} + +static std::string +make_name(pgp_symm_alg_t cipher, pgp_cipher_mode_t mode) +{ + const char *cipher_string = id_str_pair::lookup(cipher_map, cipher, NULL); + const char *mode_string = id_str_pair::lookup(cipher_mode_map, mode, NULL); + if (!cipher_string || !mode_string) { + return ""; + } + return std::string(cipher_string) + "-" + mode_string; +} + +std::unique_ptr<Cipher_OpenSSL> +Cipher_OpenSSL::encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ + EVP_CIPHER_CTX *ossl_ctx = + create(cipher, make_name(cipher, mode), true, tag_size, disable_padding); + if (!ossl_ctx) { + return NULL; + } + return std::unique_ptr<Cipher_OpenSSL>(new (std::nothrow) + Cipher_OpenSSL(cipher, ossl_ctx, tag_size, true)); +} + +std::unique_ptr<Cipher_OpenSSL> +Cipher_OpenSSL::decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ + EVP_CIPHER_CTX *ossl_ctx = + create(cipher, make_name(cipher, mode), false, tag_size, disable_padding); + if (!ossl_ctx) { + return NULL; + } + return std::unique_ptr<Cipher_OpenSSL>( + new (std::nothrow) Cipher_OpenSSL(cipher, ossl_ctx, tag_size, false)); +} + +bool +Cipher_OpenSSL::set_key(const uint8_t *key, size_t key_length) +{ + assert(key_length <= INT_MAX); + return EVP_CIPHER_CTX_set_key_length(m_ctx, (int) key_length) == 1 && + EVP_CipherInit_ex(m_ctx, NULL, NULL, key, NULL, -1) == 1; +} + +bool +Cipher_OpenSSL::set_iv(const uint8_t *iv, size_t iv_length) +{ + assert(iv_length <= INT_MAX); + // set IV len for AEAD modes + if (m_tag_size && + EVP_CIPHER_CTX_ctrl(m_ctx, EVP_CTRL_AEAD_SET_IVLEN, (int) iv_length, NULL) != 1) { + RNP_LOG("Failed to set AEAD IV length: %lu", ERR_peek_last_error()); + return false; + } + if (EVP_CIPHER_CTX_iv_length(m_ctx) != (int) iv_length) { + RNP_LOG("IV length mismatch"); + return false; + } + if (EVP_CipherInit_ex(m_ctx, NULL, NULL, NULL, iv, -1) != 1) { + RNP_LOG("Failed to set IV: %lu", ERR_peek_last_error()); + } + return true; +} + +bool +Cipher_OpenSSL::set_ad(const uint8_t *ad, size_t ad_length) +{ + assert(m_tag_size); + int outlen = 0; + if (EVP_CipherUpdate(m_ctx, NULL, &outlen, ad, ad_length) != 1) { + RNP_LOG("Failed to set AD: %lu", ERR_peek_last_error()); + return false; + } + return true; +} + +size_t +Cipher_OpenSSL::update_granularity() const +{ + return (size_t) EVP_CIPHER_CTX_block_size(m_ctx); +} + +bool +Cipher_OpenSSL::update(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) +{ + if (input_length > INT_MAX) { + return false; + } + *input_consumed = 0; + *output_written = 0; + if (input_length == 0) { + return true; + } + int outl = 0; + if (EVP_CipherUpdate(m_ctx, output, &outl, input, (int) input_length) != 1) { + RNP_LOG("EVP_CipherUpdate failed: %lu", ERR_peek_last_error()); + return false; + } + assert((size_t) outl < output_length); + *input_consumed = input_length; + *output_written = (size_t) outl; + return true; +} + +bool +Cipher_OpenSSL::finish(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) +{ + if (input_length > INT_MAX) { + return false; + } + if (!m_encrypt && input_length < m_tag_size) { + RNP_LOG("Insufficient input for final block (missing tag)"); + return false; + } + *input_consumed = 0; + *output_written = 0; + if (!m_encrypt && m_tag_size) { + // set the tag from the end of the ciphertext + if (EVP_CIPHER_CTX_ctrl(m_ctx, + EVP_CTRL_AEAD_SET_TAG, + m_tag_size, + const_cast<uint8_t *>(input) + input_length - m_tag_size) != + 1) { + RNP_LOG("Failed to set expected AEAD tag: %lu", ERR_peek_last_error()); + return false; + } + size_t ats = std::min(m_tag_size, input_length); + input_length -= ats; // m_tag_size; + *input_consumed += ats; // m_tag_size; + } + int outl = 0; + if (EVP_CipherUpdate(m_ctx, output, &outl, input, (int) input_length) != 1) { + RNP_LOG("EVP_CipherUpdate failed: %lu", ERR_peek_last_error()); + return false; + } + input += input_length; + *input_consumed += input_length; + output += outl; + *output_written += (size_t) outl; + if (EVP_CipherFinal_ex(m_ctx, output, &outl) != 1) { + RNP_LOG("EVP_CipherFinal_ex failed: %lu", ERR_peek_last_error()); + return false; + } + *output_written += (size_t) outl; + output += (size_t) outl; + if (m_encrypt && m_tag_size) { + // append the tag + if (EVP_CIPHER_CTX_ctrl(m_ctx, EVP_CTRL_AEAD_GET_TAG, m_tag_size, output) != 1) { + RNP_LOG("Failed to append AEAD tag: %lu", ERR_peek_last_error()); + return false; + } + *output_written += m_tag_size; + } + return true; +} + +Cipher_OpenSSL::Cipher_OpenSSL(pgp_symm_alg_t alg, + EVP_CIPHER_CTX *ctx, + size_t tag_size, + bool encrypt) + : Cipher(alg), m_ctx(ctx), m_tag_size(tag_size), m_encrypt(encrypt) +{ + m_block_size = EVP_CIPHER_CTX_block_size(m_ctx); +} + +Cipher_OpenSSL::~Cipher_OpenSSL() +{ + EVP_CIPHER_CTX_free(m_ctx); +} diff --git a/src/lib/crypto/cipher_ossl.hpp b/src/lib/crypto/cipher_ossl.hpp new file mode 100644 index 0000000..da2bb4e --- /dev/null +++ b/src/lib/crypto/cipher_ossl.hpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_CIPHER_OSSL_HPP +#define RNP_CIPHER_OSSL_HPP + +#include "cipher.hpp" +#include <openssl/evp.h> +#include <repgp/repgp_def.h> + +class Cipher_OpenSSL : public Cipher { + public: + static std::unique_ptr<Cipher_OpenSSL> encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding); + static std::unique_ptr<Cipher_OpenSSL> decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding); + + bool set_key(const uint8_t *key, size_t key_length) override; + bool set_iv(const uint8_t *iv, size_t iv_length) override; + bool set_ad(const uint8_t *ad, size_t ad_length) override; + + size_t update_granularity() const override; + + // input_length should not exceed INT_MAX + bool update(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) override; + bool finish(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) override; + virtual ~Cipher_OpenSSL(); + + private: + Cipher_OpenSSL(pgp_symm_alg_t alg, EVP_CIPHER_CTX *ctx, size_t tag_size, bool encrypt); + + EVP_CIPHER_CTX *m_ctx; + size_t m_block_size; + size_t m_tag_size; + bool m_encrypt; + + static EVP_CIPHER_CTX *create(pgp_symm_alg_t alg, + const std::string &name, + bool encrypt, + size_t tag_size, + bool disable_padding); +}; + +#endif diff --git a/src/lib/crypto/common.h b/src/lib/crypto/common.h new file mode 100644 index 0000000..3d9b837 --- /dev/null +++ b/src/lib/crypto/common.h @@ -0,0 +1,51 @@ +/*- + * Copyright (c) 2018 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_CRYPTO_COMMON_H_ +#define RNP_CRYPTO_COMMON_H_ + +/* base */ +#include "mpi.h" +#include "rng.h" +/* asymmetric crypto */ +#include "rsa.h" +#include "dsa.h" +#include "elgamal.h" +#include "ec.h" +#include "ecdh.h" +#include "ecdsa.h" +#include "sm2.h" +#include "eddsa.h" +/* symmetric crypto */ +#include "symmetric.h" +/* hash */ +#include "hash.hpp" +/* s2k */ +#include "s2k.h" +/* backend name and version */ +#include "backend_version.h" + +#endif // RNP_CRYPTO_COMMON_H_ diff --git a/src/lib/crypto/dl_ossl.cpp b/src/lib/crypto/dl_ossl.cpp new file mode 100644 index 0000000..1e96218 --- /dev/null +++ b/src/lib/crypto/dl_ossl.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <cstdlib> +#include <string> +#include <cassert> +#include "bn.h" +#include "dl_ossl.h" +#include "utils.h" +#include <openssl/dh.h> +#include <openssl/err.h> +#include <openssl/evp.h> + +EVP_PKEY * +dl_load_key(const pgp_mpi_t &mp, + const pgp_mpi_t *mq, + const pgp_mpi_t &mg, + const pgp_mpi_t &my, + const pgp_mpi_t *mx) +{ + DH * dh = NULL; + EVP_PKEY *evpkey = NULL; + bignum_t *p = mpi2bn(&mp); + bignum_t *q = mq ? mpi2bn(mq) : NULL; + bignum_t *g = mpi2bn(&mg); + bignum_t *y = mpi2bn(&my); + bignum_t *x = mx ? mpi2bn(mx) : NULL; + + if (!p || (mq && !q) || !g || !y || (mx && !x)) { + RNP_LOG("out of memory"); + goto done; + } + + dh = DH_new(); + if (!dh) { + RNP_LOG("out of memory"); + goto done; + } + int res; + /* line below must not fail */ + res = DH_set0_pqg(dh, p, q, g); + assert(res == 1); + if (res < 1) { + goto done; + } + p = NULL; + q = NULL; + g = NULL; + /* line below must not fail */ + res = DH_set0_key(dh, y, x); + assert(res == 1); + if (res < 1) { + goto done; + } + y = NULL; + x = NULL; + + evpkey = EVP_PKEY_new(); + if (!evpkey) { + RNP_LOG("allocation failed"); + goto done; + } + if (EVP_PKEY_set1_DH(evpkey, dh) <= 0) { + RNP_LOG("Failed to set key: %lu", ERR_peek_last_error()); + EVP_PKEY_free(evpkey); + evpkey = NULL; + } +done: + DH_free(dh); + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(y); + bn_free(x); + return evpkey; +} + +static rnp_result_t +dl_validate_secret_key(EVP_PKEY *dlkey, const pgp_mpi_t &mx) +{ + const DH *dh = EVP_PKEY_get0_DH(dlkey); + assert(dh); + const bignum_t *p = DH_get0_p(dh); + const bignum_t *q = DH_get0_q(dh); + const bignum_t *g = DH_get0_g(dh); + const bignum_t *y = DH_get0_pub_key(dh); + assert(p && g && y); + bignum_t *p1 = NULL; + + rnp_result_t ret = RNP_ERROR_GENERIC; + + BN_CTX * ctx = BN_CTX_new(); + bignum_t *x = mpi2bn(&mx); + bignum_t *cy = bn_new(); + + if (!x || !cy || !ctx) { + RNP_LOG("Allocation failed"); + goto done; + } + if (!q) { + /* if q is NULL then group order is (p - 1) / 2 */ + p1 = BN_dup(p); + if (!p1) { + RNP_LOG("Allocation failed"); + goto done; + } + int res; + res = BN_rshift(p1, p1, 1); + assert(res == 1); + if (res < 1) { + RNP_LOG("BN_rshift failed."); + goto done; + } + q = p1; + } + if (BN_cmp(x, q) != -1) { + RNP_LOG("x is too large."); + goto done; + } + if (BN_mod_exp_mont_consttime(cy, g, x, p, ctx, NULL) < 1) { + RNP_LOG("Exponentiation failed"); + goto done; + } + if (BN_cmp(cy, y) == 0) { + ret = RNP_SUCCESS; + } +done: + BN_CTX_free(ctx); + bn_free(x); + bn_free(cy); + bn_free(p1); + return ret; +} + +rnp_result_t +dl_validate_key(EVP_PKEY *pkey, const pgp_mpi_t *x) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + int res; + res = EVP_PKEY_param_check(ctx); + if (res < 0) { + RNP_LOG("Param validation error: %lu (%s)", + ERR_peek_last_error(), + ERR_reason_error_string(ERR_peek_last_error())); + } + if (res < 1) { + /* ElGamal specification doesn't seem to restrict P to the safe prime */ + auto err = ERR_peek_last_error(); + DHerr(DH_F_DH_CHECK_EX, DH_R_CHECK_P_NOT_SAFE_PRIME); + if ((ERR_GET_REASON(err) == DH_R_CHECK_P_NOT_SAFE_PRIME)) { + RNP_LOG("Warning! P is not a safe prime."); + } else { + goto done; + } + } + res = EVP_PKEY_public_check(ctx); + if (res < 0) { + RNP_LOG("Key validation error: %lu", ERR_peek_last_error()); + } + if (res < 1) { + goto done; + } + /* There is no private key check in OpenSSL yet, so need to check x vs y manually */ + if (!x) { + ret = RNP_SUCCESS; + goto done; + } + ret = dl_validate_secret_key(pkey, *x); +done: + EVP_PKEY_CTX_free(ctx); + return ret; +} diff --git a/src/lib/crypto/dl_ossl.h b/src/lib/crypto/dl_ossl.h new file mode 100644 index 0000000..fcafc0a --- /dev/null +++ b/src/lib/crypto/dl_ossl.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DL_OSSL_H_ +#define DL_OSSL_H_ + +#include "types.h" +#include "config.h" +#include <rnp/rnp_def.h> +#include "mpi.h" +#include <openssl/evp.h> + +EVP_PKEY *dl_load_key(const pgp_mpi_t &mp, + const pgp_mpi_t *mq, + const pgp_mpi_t &mg, + const pgp_mpi_t &my, + const pgp_mpi_t *mx); + +rnp_result_t dl_validate_key(EVP_PKEY *pkey, const pgp_mpi_t *mx); + +#endif diff --git a/src/lib/crypto/dsa.cpp b/src/lib/crypto/dsa.cpp new file mode 100644 index 0000000..8763f00 --- /dev/null +++ b/src/lib/crypto/dsa.cpp @@ -0,0 +1,382 @@ +/*- + * Copyright (c) 2017-2018 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Alistair Crooks (agc@NetBSD.org) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** \file + */ +#include <stdlib.h> +#include <string.h> +#include <botan/ffi.h> +#include <rnp/rnp_def.h> +#include "dsa.h" +#include "bn.h" +#include "utils.h" + +#define DSA_MAX_Q_BITLEN 256 + +rnp_result_t +dsa_validate_key(rnp::RNG *rng, const pgp_dsa_key_t *key, bool secret) +{ + bignum_t * p = NULL; + bignum_t * q = NULL; + bignum_t * g = NULL; + bignum_t * y = NULL; + bignum_t * x = NULL; + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + + /* load and check public key part */ + p = mpi2bn(&key->p); + q = mpi2bn(&key->q); + g = mpi2bn(&key->g); + y = mpi2bn(&key->y); + + if (!p || !q || !g || !y) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (botan_pubkey_load_dsa( + &bpkey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(y))) { + goto done; + } + + if (botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + /* load and check secret key part */ + if (!(x = mpi2bn(&key->x))) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (botan_privkey_load_dsa( + &bskey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(x))) { + goto done; + } + + if (botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + + ret = RNP_SUCCESS; +done: + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(y); + bn_free(x); + botan_privkey_destroy(bskey); + botan_pubkey_destroy(bpkey); + return ret; +} + +rnp_result_t +dsa_sign(rnp::RNG * rng, + pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t *key) +{ + botan_privkey_t dsa_key = NULL; + botan_pk_op_sign_t sign_op = NULL; + size_t q_order = 0; + uint8_t sign_buf[2 * BITS_TO_BYTES(DSA_MAX_Q_BITLEN)] = {0}; + bignum_t * p = NULL, *q = NULL, *g = NULL, *x = NULL; + rnp_result_t ret = RNP_ERROR_SIGNING_FAILED; + size_t sigbuf_size = sizeof(sign_buf); + + size_t z_len = 0; + + memset(sig, 0, sizeof(*sig)); + q_order = mpi_bytes(&key->q); + if ((2 * q_order) > sizeof(sign_buf)) { + RNP_LOG("wrong q order"); + return RNP_ERROR_BAD_PARAMETERS; + } + + // As 'Raw' is used we need to reduce hash size (as per FIPS-186-4, 4.6) + z_len = hash_len < q_order ? hash_len : q_order; + + p = mpi2bn(&key->p); + q = mpi2bn(&key->q); + g = mpi2bn(&key->g); + x = mpi2bn(&key->x); + + if (!p || !q || !g || !x) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + + if (botan_privkey_load_dsa( + &dsa_key, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(x))) { + RNP_LOG("Can't load key"); + goto end; + } + + if (botan_pk_op_sign_create(&sign_op, dsa_key, "Raw", 0)) { + goto end; + } + + if (botan_pk_op_sign_update(sign_op, hash, z_len)) { + goto end; + } + + if (botan_pk_op_sign_finish(sign_op, rng->handle(), sign_buf, &sigbuf_size)) { + RNP_LOG("Signing has failed"); + goto end; + } + + // Now load the DSA (r,s) values from the signature. + if (!mem2mpi(&sig->r, sign_buf, q_order) || + !mem2mpi(&sig->s, sign_buf + q_order, q_order)) { + goto end; + } + ret = RNP_SUCCESS; + +end: + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(x); + botan_pk_op_sign_destroy(sign_op); + botan_privkey_destroy(dsa_key); + return ret; +} + +rnp_result_t +dsa_verify(const pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t * key) +{ + botan_pubkey_t dsa_key = NULL; + botan_pk_op_verify_t verify_op = NULL; + uint8_t sign_buf[2 * BITS_TO_BYTES(DSA_MAX_Q_BITLEN)] = {0}; + size_t q_order = 0; + size_t r_blen, s_blen; + bignum_t * p = NULL, *q = NULL, *g = NULL, *y = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + size_t z_len = 0; + + q_order = mpi_bytes(&key->q); + if ((2 * q_order) > sizeof(sign_buf)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + z_len = hash_len < q_order ? hash_len : q_order; + + r_blen = mpi_bytes(&sig->r); + s_blen = mpi_bytes(&sig->s); + if ((r_blen > q_order) || (s_blen > q_order)) { + RNP_LOG("Wrong signature"); + return RNP_ERROR_BAD_PARAMETERS; + } + + p = mpi2bn(&key->p); + q = mpi2bn(&key->q); + g = mpi2bn(&key->g); + y = mpi2bn(&key->y); + + if (!p || !q || !g || !y) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + + if (botan_pubkey_load_dsa( + &dsa_key, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(y))) { + RNP_LOG("Wrong key"); + goto end; + } + + mpi2mem(&sig->r, sign_buf + q_order - r_blen); + mpi2mem(&sig->s, sign_buf + 2 * q_order - s_blen); + + if (botan_pk_op_verify_create(&verify_op, dsa_key, "Raw", 0)) { + RNP_LOG("Can't create verifier"); + goto end; + } + + if (botan_pk_op_verify_update(verify_op, hash, z_len)) { + goto end; + } + + ret = (botan_pk_op_verify_finish(verify_op, sign_buf, 2 * q_order) == BOTAN_FFI_SUCCESS) ? + RNP_SUCCESS : + RNP_ERROR_SIGNATURE_INVALID; + +end: + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(y); + botan_pk_op_verify_destroy(verify_op); + botan_pubkey_destroy(dsa_key); + return ret; +} + +rnp_result_t +dsa_generate(rnp::RNG *rng, pgp_dsa_key_t *key, size_t keylen, size_t qbits) +{ + if ((keylen < 1024) || (keylen > 3072) || (qbits < 160) || (qbits > 256)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + botan_privkey_t key_priv = NULL; + botan_pubkey_t key_pub = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + bignum_t * p = bn_new(); + bignum_t * q = bn_new(); + bignum_t * g = bn_new(); + bignum_t * y = bn_new(); + bignum_t * x = bn_new(); + + if (!p || !q || !g || !y || !x) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + + if (botan_privkey_create_dsa(&key_priv, rng->handle(), keylen, qbits) || + botan_privkey_check_key(key_priv, rng->handle(), 1) || + botan_privkey_export_pubkey(&key_pub, key_priv)) { + RNP_LOG("Wrong parameters"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + + if (botan_pubkey_get_field(BN_HANDLE_PTR(p), key_pub, "p") || + botan_pubkey_get_field(BN_HANDLE_PTR(q), key_pub, "q") || + botan_pubkey_get_field(BN_HANDLE_PTR(g), key_pub, "g") || + botan_pubkey_get_field(BN_HANDLE_PTR(y), key_pub, "y") || + botan_privkey_get_field(BN_HANDLE_PTR(x), key_priv, "x")) { + RNP_LOG("Botan FFI call failed"); + ret = RNP_ERROR_GENERIC; + goto end; + } + + if (!bn2mpi(p, &key->p) || !bn2mpi(q, &key->q) || !bn2mpi(g, &key->g) || + !bn2mpi(y, &key->y) || !bn2mpi(x, &key->x)) { + RNP_LOG("failed to copy mpi"); + goto end; + } + ret = RNP_SUCCESS; +end: + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(y); + bn_free(x); + botan_privkey_destroy(key_priv); + botan_pubkey_destroy(key_pub); + return ret; +} + +pgp_hash_alg_t +dsa_get_min_hash(size_t qsize) +{ + /* + * I'm using _broken_ SHA1 here only because + * some old implementations may not understand keys created + * with other hashes. If you're sure we don't have to support + * such implementations, please be my guest and remove it. + */ + return (qsize < 160) ? PGP_HASH_UNKNOWN : + (qsize == 160) ? PGP_HASH_SHA1 : + (qsize <= 224) ? PGP_HASH_SHA224 : + (qsize <= 256) ? PGP_HASH_SHA256 : + (qsize <= 384) ? PGP_HASH_SHA384 : + (qsize <= 512) ? PGP_HASH_SHA512 + /*(qsize>512)*/ : + PGP_HASH_UNKNOWN; +} + +size_t +dsa_choose_qsize_by_psize(size_t psize) +{ + return (psize == 1024) ? 160 : + (psize <= 2047) ? 224 : + (psize <= 3072) ? DSA_MAX_Q_BITLEN : + 0; +} diff --git a/src/lib/crypto/dsa.h b/src/lib/crypto/dsa.h new file mode 100644 index 0000000..52a186a --- /dev/null +++ b/src/lib/crypto/dsa.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2017-2018, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_DSA_H_ +#define RNP_DSA_H_ + +#include <rnp/rnp_def.h> +#include <repgp/repgp_def.h> +#include "crypto/rng.h" +#include "crypto/mpi.h" + +typedef struct pgp_dsa_key_t { + pgp_mpi_t p; + pgp_mpi_t q; + pgp_mpi_t g; + pgp_mpi_t y; + /* secret mpi */ + pgp_mpi_t x; +} pgp_dsa_key_t; + +typedef struct pgp_dsa_signature_t { + pgp_mpi_t r; + pgp_mpi_t s; +} pgp_dsa_signature_t; + +/** + * @brief Checks DSA key fields for validity + * + * @param rng initialized PRNG + * @param key initialized DSA key structure + * @param secret flag which tells whether key has populated secret fields + * + * @return RNP_SUCCESS if key is valid or error code otherwise + */ +rnp_result_t dsa_validate_key(rnp::RNG *rng, const pgp_dsa_key_t *key, bool secret); + +/* + * @brief Performs DSA signing + * + * @param rng initialized PRNG + * @param sig[out] created signature + * @param hash hash to sign + * @param hash_len length of `hash` + * @param key DSA key (must include secret mpi) + * + * @returns RNP_SUCCESS + * RNP_ERROR_BAD_PARAMETERS wrong input provided + * RNP_ERROR_SIGNING_FAILED internal error + */ +rnp_result_t dsa_sign(rnp::RNG * rng, + pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t *key); + +/* + * @brief Performs DSA verification + * + * @param hash hash to verify + * @param hash_len length of `hash` + * @param sig signature to be verified + * @param key DSA key (secret mpi is not needed) + * + * @returns RNP_SUCCESS + * RNP_ERROR_BAD_PARAMETERS wrong input provided + * RNP_ERROR_GENERIC internal error + * RNP_ERROR_SIGNATURE_INVALID signature is invalid + */ +rnp_result_t dsa_verify(const pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t * key); + +/* + * @brief Performs DSA key generation + * + * @param rng initialized PRNG + * @param key[out] generated key data will be stored here + * @param keylen length of the key, in bits + * @param qbits subgroup size in bits + * + * @returns RNP_SUCCESS + * RNP_ERROR_BAD_PARAMETERS wrong input provided + * RNP_ERROR_OUT_OF_MEMORY memory allocation failed + * RNP_ERROR_GENERIC internal error + * RNP_ERROR_SIGNATURE_INVALID signature is invalid + */ +rnp_result_t dsa_generate(rnp::RNG *rng, pgp_dsa_key_t *key, size_t keylen, size_t qbits); + +/* + * @brief Returns minimally sized hash which will work + * with the DSA subgroup. + * + * @param qsize subgroup order + * + * @returns Either ID of the hash algorithm, or PGP_HASH_UNKNOWN + * if not found + */ +pgp_hash_alg_t dsa_get_min_hash(size_t qsize); + +/* + * @brief Helps to determine subgroup size by size of p + * In order not to confuse users, we use less complicated + * approach than suggested by FIPS-186, which is: + * p=1024 => q=160 + * p<2048 => q=224 + * p<=3072 => q=256 + * So we don't generate (2048, 224) pair + * + * @return Size of `q' or 0 in case `psize' is not in <1024,3072> range + */ +size_t dsa_choose_qsize_by_psize(size_t psize); + +#endif diff --git a/src/lib/crypto/dsa_ossl.cpp b/src/lib/crypto/dsa_ossl.cpp new file mode 100644 index 0000000..1fb75b5 --- /dev/null +++ b/src/lib/crypto/dsa_ossl.cpp @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <string.h> +#include <rnp/rnp_def.h> +#include "bn.h" +#include "dsa.h" +#include "dl_ossl.h" +#include "utils.h" +#include <openssl/dsa.h> +#include <openssl/dh.h> +#include <openssl/err.h> +#include <openssl/evp.h> + +#define DSA_MAX_Q_BITLEN 256 + +static bool +dsa_decode_sig(const uint8_t *data, size_t len, pgp_dsa_signature_t &sig) +{ + DSA_SIG *dsig = d2i_DSA_SIG(NULL, &data, len); + if (!dsig) { + RNP_LOG("Failed to parse DSA sig: %lu", ERR_peek_last_error()); + return false; + } + const BIGNUM *r, *s; + DSA_SIG_get0(dsig, &r, &s); + bn2mpi(r, &sig.r); + bn2mpi(s, &sig.s); + DSA_SIG_free(dsig); + return true; +} + +static bool +dsa_encode_sig(uint8_t *data, size_t *len, const pgp_dsa_signature_t &sig) +{ + bool res = false; + DSA_SIG *dsig = DSA_SIG_new(); + BIGNUM * r = mpi2bn(&sig.r); + BIGNUM * s = mpi2bn(&sig.s); + if (!dsig || !r || !s) { + RNP_LOG("Allocation failed."); + goto done; + } + DSA_SIG_set0(dsig, r, s); + r = NULL; + s = NULL; + int outlen; + outlen = i2d_DSA_SIG(dsig, &data); + if (outlen < 0) { + RNP_LOG("Failed to encode signature."); + goto done; + } + *len = outlen; + res = true; +done: + DSA_SIG_free(dsig); + BN_free(r); + BN_free(s); + return res; +} + +static EVP_PKEY * +dsa_load_key(const pgp_dsa_key_t *key, bool secret = false) +{ + DSA * dsa = NULL; + EVP_PKEY *evpkey = NULL; + bignum_t *p = mpi2bn(&key->p); + bignum_t *q = mpi2bn(&key->q); + bignum_t *g = mpi2bn(&key->g); + bignum_t *y = mpi2bn(&key->y); + bignum_t *x = secret ? mpi2bn(&key->x) : NULL; + + if (!p || !q || !g || !y || (secret && !x)) { + RNP_LOG("out of memory"); + goto done; + } + + dsa = DSA_new(); + if (!dsa) { + RNP_LOG("Out of memory"); + goto done; + } + if (DSA_set0_pqg(dsa, p, q, g) != 1) { + RNP_LOG("Failed to set pqg. Error: %lu", ERR_peek_last_error()); + goto done; + } + p = NULL; + q = NULL; + g = NULL; + if (DSA_set0_key(dsa, y, x) != 1) { + RNP_LOG("Secret key load error: %lu", ERR_peek_last_error()); + goto done; + } + y = NULL; + x = NULL; + + evpkey = EVP_PKEY_new(); + if (!evpkey) { + RNP_LOG("allocation failed"); + goto done; + } + if (EVP_PKEY_set1_DSA(evpkey, dsa) <= 0) { + RNP_LOG("Failed to set key: %lu", ERR_peek_last_error()); + EVP_PKEY_free(evpkey); + evpkey = NULL; + } +done: + DSA_free(dsa); + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(y); + bn_free(x); + return evpkey; +} + +rnp_result_t +dsa_validate_key(rnp::RNG *rng, const pgp_dsa_key_t *key, bool secret) +{ + /* OpenSSL doesn't implement key checks for the DSA, however we may use DL via DH */ + EVP_PKEY *pkey = dl_load_key(key->p, &key->q, key->g, key->y, NULL); + if (!pkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_result_t ret = dl_validate_key(pkey, secret ? &key->x : NULL); + EVP_PKEY_free(pkey); + return ret; +} + +rnp_result_t +dsa_sign(rnp::RNG * rng, + pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t *key) +{ + if (mpi_bytes(&key->x) == 0) { + RNP_LOG("private key not set"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* Load secret key to DSA structure*/ + EVP_PKEY *evpkey = dsa_load_key(key, true); + if (!evpkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + /* init context and sign */ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_sign_init(ctx) <= 0) { + RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error()); + goto done; + } + sig->s.len = PGP_MPINT_SIZE; + if (EVP_PKEY_sign(ctx, sig->s.mpi, &sig->s.len, hash, hash_len) <= 0) { + RNP_LOG("Signing failed: %lu", ERR_peek_last_error()); + sig->s.len = 0; + goto done; + } + if (!dsa_decode_sig(&sig->s.mpi[0], sig->s.len, *sig)) { + RNP_LOG("Failed to parse DSA sig: %lu", ERR_peek_last_error()); + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(evpkey); + return ret; +} + +rnp_result_t +dsa_verify(const pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t * key) +{ + /* Load secret key to DSA structure*/ + EVP_PKEY *evpkey = dsa_load_key(key, false); + if (!evpkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + /* init context and sign */ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_verify_init(ctx) <= 0) { + RNP_LOG("Failed to initialize verify: %lu", ERR_peek_last_error()); + goto done; + } + pgp_mpi_t sigbuf; + if (!dsa_encode_sig(sigbuf.mpi, &sigbuf.len, *sig)) { + goto done; + } + if (EVP_PKEY_verify(ctx, sigbuf.mpi, sigbuf.len, hash, hash_len) <= 0) { + ret = RNP_ERROR_SIGNATURE_INVALID; + } else { + ret = RNP_SUCCESS; + } +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(evpkey); + return ret; +} + +rnp_result_t +dsa_generate(rnp::RNG *rng, pgp_dsa_key_t *key, size_t keylen, size_t qbits) +{ + if ((keylen < 1024) || (keylen > 3072) || (qbits < 160) || (qbits > 256)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + const DSA * dsa = NULL; + EVP_PKEY * pkey = NULL; + EVP_PKEY * parmkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + + /* Generate DSA params */ + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DSA, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + return ret; + } + if (EVP_PKEY_paramgen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx, keylen) <= 0) { + RNP_LOG("Failed to set key bits: %lu", ERR_peek_last_error()); + goto done; + } +#if OPENSSL_VERSION_NUMBER < 0x1010105fL + EVP_PKEY_CTX_ctrl( + ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, qbits, NULL); +#else + if (EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits) <= 0) { + RNP_LOG("Failed to set key qbits: %lu", ERR_peek_last_error()); + goto done; + } +#endif + if (EVP_PKEY_paramgen(ctx, &parmkey) <= 0) { + RNP_LOG("Failed to generate parameters: %lu", ERR_peek_last_error()); + goto done; + } + EVP_PKEY_CTX_free(ctx); + /* Generate DSA key */ + ctx = EVP_PKEY_CTX_new(parmkey, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + RNP_LOG("DSA keygen failed: %lu", ERR_peek_last_error()); + goto done; + } + dsa = EVP_PKEY_get0_DSA(pkey); + if (!dsa) { + RNP_LOG("Failed to retrieve DSA key: %lu", ERR_peek_last_error()); + goto done; + } + + const bignum_t *p; + const bignum_t *q; + const bignum_t *g; + const bignum_t *y; + const bignum_t *x; + p = DSA_get0_p(dsa); + q = DSA_get0_q(dsa); + g = DSA_get0_g(dsa); + y = DSA_get0_pub_key(dsa); + x = DSA_get0_priv_key(dsa); + if (!p || !q || !g || !y || !x) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + bn2mpi(p, &key->p); + bn2mpi(q, &key->q); + bn2mpi(g, &key->g); + bn2mpi(y, &key->y); + bn2mpi(x, &key->x); + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(parmkey); + EVP_PKEY_free(pkey); + return ret; +} + +pgp_hash_alg_t +dsa_get_min_hash(size_t qsize) +{ + /* + * I'm using _broken_ SHA1 here only because + * some old implementations may not understand keys created + * with other hashes. If you're sure we don't have to support + * such implementations, please be my guest and remove it. + */ + return (qsize < 160) ? PGP_HASH_UNKNOWN : + (qsize == 160) ? PGP_HASH_SHA1 : + (qsize <= 224) ? PGP_HASH_SHA224 : + (qsize <= 256) ? PGP_HASH_SHA256 : + (qsize <= 384) ? PGP_HASH_SHA384 : + (qsize <= 512) ? PGP_HASH_SHA512 + /*(qsize>512)*/ : + PGP_HASH_UNKNOWN; +} + +size_t +dsa_choose_qsize_by_psize(size_t psize) +{ + return (psize == 1024) ? 160 : + (psize <= 2047) ? 224 : + (psize <= 3072) ? DSA_MAX_Q_BITLEN : + 0; +} diff --git a/src/lib/crypto/ec.cpp b/src/lib/crypto/ec.cpp new file mode 100644 index 0000000..144c362 --- /dev/null +++ b/src/lib/crypto/ec.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <botan/ffi.h> +#include <string.h> +#include <cassert> +#include "ec.h" +#include "types.h" +#include "utils.h" +#include "mem.h" +#include "bn.h" + +static id_str_pair ec_algo_to_botan[] = { + {PGP_PKA_ECDH, "ECDH"}, + {PGP_PKA_ECDSA, "ECDSA"}, + {PGP_PKA_SM2, "SM2_Sig"}, + {0, NULL}, +}; + +rnp_result_t +x25519_generate(rnp::RNG *rng, pgp_ec_key_t *key) +{ + botan_privkey_t pr_key = NULL; + botan_pubkey_t pu_key = NULL; + rnp_result_t ret = RNP_ERROR_KEY_GENERATION; + + rnp::secure_array<uint8_t, 32> keyle; + + if (botan_privkey_create(&pr_key, "Curve25519", "", rng->handle())) { + goto end; + } + + if (botan_privkey_export_pubkey(&pu_key, pr_key)) { + goto end; + } + + /* botan returns key in little-endian, while mpi is big-endian */ + if (botan_privkey_x25519_get_privkey(pr_key, keyle.data())) { + goto end; + } + for (int i = 0; i < 32; i++) { + key->x.mpi[31 - i] = keyle[i]; + } + key->x.len = 32; + /* botan doesn't tweak secret key bits, so we should do that here */ + if (!x25519_tweak_bits(*key)) { + goto end; + } + + if (botan_pubkey_x25519_get_pubkey(pu_key, &key->p.mpi[1])) { + goto end; + } + key->p.len = 33; + key->p.mpi[0] = 0x40; + + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(pr_key); + botan_pubkey_destroy(pu_key); + return ret; +} + +rnp_result_t +ec_generate(rnp::RNG * rng, + pgp_ec_key_t * key, + const pgp_pubkey_alg_t alg_id, + const pgp_curve_t curve) +{ + /** + * Keeps "0x04 || x || y" + * \see 13.2. ECDSA, ECDH, SM2 Conversion Primitives + * + * P-521 is biggest supported curve + */ + botan_privkey_t pr_key = NULL; + botan_pubkey_t pu_key = NULL; + bignum_t * px = NULL; + bignum_t * py = NULL; + bignum_t * x = NULL; + rnp_result_t ret = RNP_ERROR_KEY_GENERATION; + size_t filed_byte_size = 0; + + if (!alg_allows_curve(alg_id, curve)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + const char *ec_algo = id_str_pair::lookup(ec_algo_to_botan, alg_id, NULL); + assert(ec_algo); + const ec_curve_desc_t *ec_desc = get_curve_desc(curve); + if (!ec_desc) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + filed_byte_size = BITS_TO_BYTES(ec_desc->bitlen); + + // at this point it must succeed + if (botan_privkey_create(&pr_key, ec_algo, ec_desc->botan_name, rng->handle())) { + goto end; + } + + if (botan_privkey_export_pubkey(&pu_key, pr_key)) { + goto end; + } + + // Crash if seckey is null. It's clean and easy to debug design + px = bn_new(); + py = bn_new(); + x = bn_new(); + + if (!px || !py || !x) { + RNP_LOG("Allocation failed"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + + if (botan_pubkey_get_field(BN_HANDLE_PTR(px), pu_key, "public_x")) { + goto end; + } + + if (botan_pubkey_get_field(BN_HANDLE_PTR(py), pu_key, "public_y")) { + goto end; + } + + if (botan_privkey_get_field(BN_HANDLE_PTR(x), pr_key, "x")) { + goto end; + } + + size_t x_bytes; + size_t y_bytes; + x_bytes = bn_num_bytes(*px); + y_bytes = bn_num_bytes(*py); + + // Safety check + if ((x_bytes > filed_byte_size) || (y_bytes > filed_byte_size)) { + RNP_LOG("Key generation failed"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + + /* + * Convert coordinates to MPI stored as + * "0x04 || x || y" + * + * \see 13.2. ECDSA and ECDH Conversion Primitives + * + * Note: Generated pk/sk may not always have exact number of bytes + * which is important when converting to octet-string + */ + memset(key->p.mpi, 0, sizeof(key->p.mpi)); + key->p.mpi[0] = 0x04; + bn_bn2bin(px, &key->p.mpi[1 + filed_byte_size - x_bytes]); + bn_bn2bin(py, &key->p.mpi[1 + filed_byte_size + (filed_byte_size - y_bytes)]); + key->p.len = 2 * filed_byte_size + 1; + /* secret key value */ + bn2mpi(x, &key->x); + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(pr_key); + botan_pubkey_destroy(pu_key); + bn_free(px); + bn_free(py); + bn_free(x); + return ret; +} diff --git a/src/lib/crypto/ec.h b/src/lib/crypto/ec.h new file mode 100644 index 0000000..07cb8e8 --- /dev/null +++ b/src/lib/crypto/ec.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef EC_H_ +#define EC_H_ + +#include "config.h" +#include <rnp/rnp_def.h> +#include <repgp/repgp_def.h> +#include "crypto/rng.h" +#include "crypto/mpi.h" + +#define MAX_CURVE_BIT_SIZE 521 // secp521r1 +/* Maximal byte size of elliptic curve order (NIST P-521) */ +#define MAX_CURVE_BYTELEN ((MAX_CURVE_BIT_SIZE + 7) / 8) + +/** + * Maximal length of the OID in hex representation. + * + * \see RFC4880 bis01 - 9.2 ECC Curve OID + */ +#define MAX_CURVE_OID_HEX_LEN 10U + +/** + * Structure holds description of elliptic curve + */ +typedef struct ec_curve_desc_t { + const pgp_curve_t rnp_curve_id; + const size_t bitlen; + const uint8_t OIDhex[MAX_CURVE_OID_HEX_LEN]; + const size_t OIDhex_len; +#if defined(CRYPTO_BACKEND_BOTAN) + const char *botan_name; +#endif +#if defined(CRYPTO_BACKEND_OPENSSL) + const char *openssl_name; +#endif + const char *pgp_name; + /* Curve is supported for keygen/sign/encrypt operations */ + bool supported; + /* Curve parameters below. Needed for grip calculation */ + const char *p; + const char *a; + const char *b; + const char *n; + const char *gx; + const char *gy; + const char *h; +} ec_curve_desc_t; + +typedef struct pgp_ec_key_t { + pgp_curve_t curve; + pgp_mpi_t p; + /* secret mpi */ + pgp_mpi_t x; + /* ecdh params */ + pgp_hash_alg_t kdf_hash_alg; /* Hash used by kdf */ + pgp_symm_alg_t key_wrap_alg; /* Symmetric algorithm used to wrap KEK*/ +} pgp_ec_key_t; + +typedef struct pgp_ec_signature_t { + pgp_mpi_t r; + pgp_mpi_t s; +} pgp_ec_signature_t; + +/* + * @brief Finds curve ID by hex representation of OID + * + * @param oid buffer with OID in hex + * @param oid_len length of oid buffer + * + * @returns success curve ID + * failure PGP_CURVE_MAX is returned + * + * @remarks see RFC 4880 bis 01 - 9.2 ECC Curve OID + */ +pgp_curve_t find_curve_by_OID(const uint8_t *oid, size_t oid_len); + +pgp_curve_t find_curve_by_name(const char *name); + +/* + * @brief Returns pointer to the curve descriptor + * + * @param Valid curve ID + * + * @returns NULL if wrong ID provided, otherwise descriptor + * + */ +const ec_curve_desc_t *get_curve_desc(const pgp_curve_t curve_id); + +bool alg_allows_curve(pgp_pubkey_alg_t alg, pgp_curve_t curve); + +/** + * @brief Check whether curve is supported for operations. + * All available curves are supported for reading/parsing key data, however some of them + * may be disabled for use, i.e. for key generation/signing/encryption. + */ +bool curve_supported(pgp_curve_t curve); + +/* + * @brief Generates EC key in uncompressed format + * + * @param rng initialized rnp::RNG context* + * @param key key data to be generated + * @param alg_id ID of EC algorithm + * @param curve underlying ECC curve ID + * + * @pre alg_id MUST be supported algorithm + * + * @returns RNP_ERROR_BAD_PARAMETERS unknown curve_id + * @returns RNP_ERROR_OUT_OF_MEMORY memory allocation failed + * @returns RNP_ERROR_KEY_GENERATION implementation error + */ +rnp_result_t ec_generate(rnp::RNG * rng, + pgp_ec_key_t * key, + const pgp_pubkey_alg_t alg_id, + const pgp_curve_t curve); + +/* + * @brief Generates x25519 ECDH key in x25519-specific format + * + * @param rng initialized rnp::RNG context* + * @param key key data to be generated + * + * @returns RNP_ERROR_KEY_GENERATION implementation error + */ +rnp_result_t x25519_generate(rnp::RNG *rng, pgp_ec_key_t *key); + +/** + * @brief Set least significant/most significant bits of the 25519 secret key as per + * specification. + * + * @param key secret key. + * @return true on success or false otherwise. + */ +bool x25519_tweak_bits(pgp_ec_key_t &key); + +/** + * @brief Check whether least significant/most significant bits of 25519 secret key are + * correctly tweaked. + * + * @param key secret key. + * @return true if bits are set correctly, and false otherwise. + */ +bool x25519_bits_tweaked(const pgp_ec_key_t &key); + +#endif diff --git a/src/lib/crypto/ec_curves.cpp b/src/lib/crypto/ec_curves.cpp new file mode 100644 index 0000000..db5cf09 --- /dev/null +++ b/src/lib/crypto/ec_curves.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include "ec.h" +#include "types.h" +#include "utils.h" +#include "str-utils.h" + +/** + * EC Curves definition used by implementation + * + * \see RFC4880 bis01 - 9.2. ECC Curve OID + * + * Order of the elements in this array corresponds to + * values in pgp_curve_t enum. + */ +static const ec_curve_desc_t ec_curves[] = { + {PGP_CURVE_UNKNOWN, 0, {0}, 0, NULL, NULL}, + + {PGP_CURVE_NIST_P_256, + 256, + {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}, + 8, +#if defined(CRYPTO_BACKEND_BOTAN) + "secp256r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "prime256v1", +#endif + "NIST P-256", + true, + "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc", + "0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", + "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", + "0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", + "0x01"}, + {PGP_CURVE_NIST_P_384, + 384, + {0x2B, 0x81, 0x04, 0x00, 0x22}, + 5, +#if defined(CRYPTO_BACKEND_BOTAN) + "secp384r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "secp384r1", +#endif + "NIST P-384", + true, + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000" + "ffffffff", + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000" + "fffffffc", + "0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8ed" + "d3ec2aef", + "0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196a" + "ccc52973", + "0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e38" + "72760ab7", + "0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c" + "90ea0e5f", + "0x01"}, + {PGP_CURVE_NIST_P_521, + 521, + {0x2B, 0x81, 0x04, 0x00, 0x23}, + 5, +#if defined(CRYPTO_BACKEND_BOTAN) + "secp521r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "secp521r1", +#endif + "NIST P-521", + true, + "0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffff", + "0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffc", + "0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652" + "c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00", + "0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc" + "0148f709a5d03bb5c9b8899c47aebb6fb71e91386409", + "0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1d" + "c127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66", + "0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550" + "b9013fad0761353c7086a272c24088be94769fd16650", + "0x01"}, + {PGP_CURVE_ED25519, + 255, + {0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01}, + 9, +#if defined(CRYPTO_BACKEND_BOTAN) + "Ed25519", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "ED25519", +#endif + "Ed25519", + true, + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", + /* two below are actually negative */ + "0x01", + "0x2dfc9311d490018c7338bf8688861767ff8ff5b2bebe27548a14b235eca6874a", + "0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed", + "0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a", + "0x6666666666666666666666666666666666666666666666666666666666666658", + "0x08"}, + {PGP_CURVE_25519, + 255, + {0x2b, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01}, + 10, +#if defined(CRYPTO_BACKEND_BOTAN) + "curve25519", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "X25519", +#endif + "Curve25519", + true, + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", + "0x01db41", + "0x01", + "0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x20ae19a1b8a086b4e01edd2c7748d14c923d4d7e6d7c61b229e9c5a27eced3d9", + "0x08"}, + {PGP_CURVE_BP256, + 256, + {0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07}, + 9, +#if defined(CRYPTO_BACKEND_BOTAN) + "brainpool256r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "brainpoolP256r1", +#endif + "brainpoolP256r1", +#if defined(ENABLE_BRAINPOOL) + true, +#else + false, +#endif + "0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377", + "0x7d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9", + "0x26dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b6", + "0xa9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7", + "0x8bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262", + "0x547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997", + "0x01"}, + {PGP_CURVE_BP384, + 384, + {0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B}, + 9, +#if defined(CRYPTO_BACKEND_BOTAN) + "brainpool384r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "brainpoolP384r1", +#endif + "brainpoolP384r1", +#if defined(ENABLE_BRAINPOOL) + true, +#else + false, +#endif + "0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a7187470013" + "3107ec53", + "0x7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8aa5814a503ad4eb04a8c7dd" + "22ce2826", + "0x04a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57cb4390295dbc9943ab78696" + "fa504c11", + "0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202" + "e9046565", + "0x1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e2" + "47d4af1e", + "0x8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341" + "263c5315", + "0x01"}, + {PGP_CURVE_BP512, + 512, + {0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D}, + 9, +#if defined(CRYPTO_BACKEND_BOTAN) + "brainpool512r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "brainpoolP512r1", +#endif + "brainpoolP512r1", +#if defined(ENABLE_BRAINPOOL) + true, +#else + false, +#endif + "0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12a" + "e6a380e62881ff2f2d82c68528aa6056583a48f3", + "0x7830a3318b603b89e2327145ac234cc594cbdd8d3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c9" + "8b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94ca", + "0x3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94ca" + "dc083e67984050b75ebae5dd2809bd638016f723", + "0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca9261941866119" + "7fac10471db1d381085ddaddb58796829ca90069", + "0x81aee4bdd82ed9645a21322e9c4c6a9385ed9f70b5d916c1b43b62eef4d0098eff3b1f78e2d0d48d50d1687b" + "93b97d5f7c6d5047406a5e688b352209bcb9f822", + "0x7dde385d566332ecc0eabfa9cf7822fdf209f70024a57b1aa000c55b881f8111b2dcde494a5f485e5bca4bd8" + "8a2763aed1ca2b2fa8f0540678cd1e0f3ad80892", + "0x01"}, + {PGP_CURVE_P256K1, + 256, + {0x2B, 0x81, 0x04, 0x00, 0x0A}, + 5, +#if defined(CRYPTO_BACKEND_BOTAN) + "secp256k1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "secp256k1", +#endif + "secp256k1", + true, + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + "0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + "0x01"}, + { + PGP_CURVE_SM2_P_256, + 256, + {0x2A, 0x81, 0x1C, 0xCF, 0x55, 0x01, 0x82, 0x2D}, + 8, +#if defined(CRYPTO_BACKEND_BOTAN) + "sm2p256v1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "sm2", +#endif + "SM2 P-256", +#if defined(ENABLE_SM2) + true, +#else + false, +#endif + "0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", + "0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", + "0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", + "0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", + "0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", + "0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", + }, +}; + +pgp_curve_t +find_curve_by_OID(const uint8_t *oid, size_t oid_len) +{ + for (size_t i = 0; i < PGP_CURVE_MAX; i++) { + if ((oid_len == ec_curves[i].OIDhex_len) && + (!memcmp(oid, ec_curves[i].OIDhex, oid_len))) { + return static_cast<pgp_curve_t>(i); + } + } + + return PGP_CURVE_MAX; +} + +pgp_curve_t +find_curve_by_name(const char *name) +{ + for (size_t i = 1; i < PGP_CURVE_MAX; i++) { + if (rnp::str_case_eq(ec_curves[i].pgp_name, name)) { + return ec_curves[i].rnp_curve_id; + } + } + + return PGP_CURVE_MAX; +} + +const ec_curve_desc_t * +get_curve_desc(const pgp_curve_t curve_id) +{ + return (curve_id < PGP_CURVE_MAX && curve_id > 0) ? &ec_curves[curve_id] : NULL; +} + +bool +alg_allows_curve(pgp_pubkey_alg_t alg, pgp_curve_t curve) +{ + /* SM2 curve is only for SM2 algo */ + if ((alg == PGP_PKA_SM2) || (curve == PGP_CURVE_SM2_P_256)) { + return (alg == PGP_PKA_SM2) && (curve == PGP_CURVE_SM2_P_256); + } + /* EDDSA and PGP_CURVE_ED25519 */ + if ((alg == PGP_PKA_EDDSA) || (curve == PGP_CURVE_ED25519)) { + return (alg == PGP_PKA_EDDSA) && (curve == PGP_CURVE_ED25519); + } + /* Curve x25519 is only for ECDH */ + if (curve == PGP_CURVE_25519) { + return alg == PGP_PKA_ECDH; + } + /* Other curves are good for both ECDH and ECDSA */ + return true; +} + +bool +curve_supported(pgp_curve_t curve) +{ + const ec_curve_desc_t *info = get_curve_desc(curve); + return info && info->supported; +} diff --git a/src/lib/crypto/ec_ossl.cpp b/src/lib/crypto/ec_ossl.cpp new file mode 100644 index 0000000..6974b4c --- /dev/null +++ b/src/lib/crypto/ec_ossl.cpp @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string> +#include <cassert> +#include "ec.h" +#include "ec_ossl.h" +#include "bn.h" +#include "types.h" +#include "mem.h" +#include "utils.h" +#include <openssl/evp.h> +#include <openssl/objects.h> +#include <openssl/err.h> +#include <openssl/ec.h> + +static bool +ec_is_raw_key(const pgp_curve_t curve) +{ + return (curve == PGP_CURVE_ED25519) || (curve == PGP_CURVE_25519); +} + +rnp_result_t +x25519_generate(rnp::RNG *rng, pgp_ec_key_t *key) +{ + return ec_generate(rng, key, PGP_PKA_ECDH, PGP_CURVE_25519); +} + +EVP_PKEY * +ec_generate_pkey(const pgp_pubkey_alg_t alg_id, const pgp_curve_t curve) +{ + if (!alg_allows_curve(alg_id, curve)) { + return NULL; + } + const ec_curve_desc_t *ec_desc = get_curve_desc(curve); + if (!ec_desc) { + return NULL; + } + int nid = OBJ_sn2nid(ec_desc->openssl_name); + if (nid == NID_undef) { + RNP_LOG("Unknown SN: %s", ec_desc->openssl_name); + return NULL; + } + bool raw = ec_is_raw_key(curve); + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(raw ? nid : EVP_PKEY_EC, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + return NULL; + } + EVP_PKEY *pkey = NULL; + if (EVP_PKEY_keygen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (!raw && (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) <= 0)) { + RNP_LOG("Failed to set curve nid: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + RNP_LOG("EC keygen failed: %lu", ERR_peek_last_error()); + } +done: + EVP_PKEY_CTX_free(ctx); + return pkey; +} + +static bool +ec_write_raw_seckey(EVP_PKEY *pkey, pgp_ec_key_t *key) +{ + /* EdDSA and X25519 keys are saved in a different way */ + static_assert(sizeof(key->x.mpi) > 32, "mpi is too small."); + key->x.len = sizeof(key->x.mpi); + if (EVP_PKEY_get_raw_private_key(pkey, key->x.mpi, &key->x.len) <= 0) { + RNP_LOG("Failed get raw private key: %lu", ERR_peek_last_error()); + return false; + } + assert(key->x.len == 32); + if (EVP_PKEY_id(pkey) == EVP_PKEY_X25519) { + /* in OpenSSL private key is exported as little-endian, while MPI is big-endian */ + for (size_t i = 0; i < 16; i++) { + std::swap(key->x.mpi[i], key->x.mpi[31 - i]); + } + } + return true; +} + +rnp_result_t +ec_generate(rnp::RNG * rng, + pgp_ec_key_t * key, + const pgp_pubkey_alg_t alg_id, + const pgp_curve_t curve) +{ + EVP_PKEY *pkey = ec_generate_pkey(alg_id, curve); + if (!pkey) { + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_result_t ret = RNP_ERROR_GENERIC; + if (ec_is_raw_key(curve)) { + if (ec_write_pubkey(pkey, key->p, curve) && ec_write_raw_seckey(pkey, key)) { + ret = RNP_SUCCESS; + } + EVP_PKEY_free(pkey); + return ret; + } + const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); + if (!ec) { + RNP_LOG("Failed to retrieve EC key: %lu", ERR_peek_last_error()); + goto done; + } + if (!ec_write_pubkey(pkey, key->p, curve)) { + RNP_LOG("Failed to write pubkey."); + goto done; + } + const bignum_t *x; + x = EC_KEY_get0_private_key(ec); + if (!x) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + if (bn2mpi(x, &key->x)) { + ret = RNP_SUCCESS; + } +done: + EVP_PKEY_free(pkey); + return ret; +} + +static EVP_PKEY * +ec_load_raw_key(const pgp_mpi_t &keyp, const pgp_mpi_t *keyx, int nid) +{ + if (!keyx) { + /* as per RFC, EdDSA & 25519 keys must use 0x40 byte for encoding */ + if ((mpi_bytes(&keyp) != 33) || (keyp.mpi[0] != 0x40)) { + RNP_LOG("Invalid 25519 public key."); + return NULL; + } + + EVP_PKEY *evpkey = + EVP_PKEY_new_raw_public_key(nid, NULL, &keyp.mpi[1], mpi_bytes(&keyp) - 1); + if (!evpkey) { + RNP_LOG("Failed to load public key: %lu", ERR_peek_last_error()); + } + return evpkey; + } + + EVP_PKEY *evpkey = NULL; + if (nid == EVP_PKEY_X25519) { + if (keyx->len != 32) { + RNP_LOG("Invalid 25519 secret key"); + return NULL; + } + /* need to reverse byte order since in mpi we have big-endian */ + rnp::secure_array<uint8_t, 32> prkey; + for (int i = 0; i < 32; i++) { + prkey[i] = keyx->mpi[31 - i]; + } + evpkey = EVP_PKEY_new_raw_private_key(nid, NULL, prkey.data(), keyx->len); + } else { + if (keyx->len > 32) { + RNP_LOG("Invalid Ed25519 secret key"); + return NULL; + } + /* keyx->len may be smaller then 32 as high byte is random and could become 0 */ + rnp::secure_array<uint8_t, 32> prkey{}; + memcpy(prkey.data() + 32 - keyx->len, keyx->mpi, keyx->len); + evpkey = EVP_PKEY_new_raw_private_key(nid, NULL, prkey.data(), 32); + } + if (!evpkey) { + RNP_LOG("Failed to load private key: %lu", ERR_peek_last_error()); + } + return evpkey; +} + +EVP_PKEY * +ec_load_key(const pgp_mpi_t &keyp, const pgp_mpi_t *keyx, pgp_curve_t curve) +{ + const ec_curve_desc_t *curv_desc = get_curve_desc(curve); + if (!curv_desc) { + RNP_LOG("unknown curve"); + return NULL; + } + if (!curve_supported(curve)) { + RNP_LOG("Curve %s is not supported.", curv_desc->pgp_name); + return NULL; + } + int nid = OBJ_sn2nid(curv_desc->openssl_name); + if (nid == NID_undef) { + RNP_LOG("Unknown SN: %s", curv_desc->openssl_name); + return NULL; + } + /* EdDSA and X25519 keys are loaded in a different way */ + if (ec_is_raw_key(curve)) { + return ec_load_raw_key(keyp, keyx, nid); + } + EC_KEY *ec = EC_KEY_new_by_curve_name(nid); + if (!ec) { + RNP_LOG("Failed to create EC key with group %d (%s): %s", + nid, + curv_desc->openssl_name, + ERR_reason_error_string(ERR_peek_last_error())); + return NULL; + } + + bool res = false; + bignum_t *x = NULL; + EVP_PKEY *pkey = NULL; + EC_POINT *p = EC_POINT_new(EC_KEY_get0_group(ec)); + if (!p) { + RNP_LOG("Failed to allocate point: %lu", ERR_peek_last_error()); + goto done; + } + if (EC_POINT_oct2point(EC_KEY_get0_group(ec), p, keyp.mpi, keyp.len, NULL) <= 0) { + RNP_LOG("Failed to decode point: %lu", ERR_peek_last_error()); + goto done; + } + if (EC_KEY_set_public_key(ec, p) <= 0) { + RNP_LOG("Failed to set public key: %lu", ERR_peek_last_error()); + goto done; + } + + pkey = EVP_PKEY_new(); + if (!pkey) { + RNP_LOG("EVP_PKEY allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + if (!keyx) { + res = true; + goto done; + } + + x = mpi2bn(keyx); + if (!x) { + RNP_LOG("allocation failed"); + goto done; + } + if (EC_KEY_set_private_key(ec, x) <= 0) { + RNP_LOG("Failed to set secret key: %lu", ERR_peek_last_error()); + goto done; + } + res = true; +done: + if (res) { + res = EVP_PKEY_set1_EC_KEY(pkey, ec) > 0; + } + EC_POINT_free(p); + BN_free(x); + EC_KEY_free(ec); + if (!res) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + return pkey; +} + +rnp_result_t +ec_validate_key(const pgp_ec_key_t &key, bool secret) +{ + if (key.curve == PGP_CURVE_25519) { + /* No key check implementation for x25519 in the OpenSSL yet, so just basic size checks + */ + if ((mpi_bytes(&key.p) != 33) || (key.p.mpi[0] != 0x40)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (secret && mpi_bytes(&key.x) != 32) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; + } + EVP_PKEY *evpkey = ec_load_key(key.p, secret ? &key.x : NULL, key.curve); + if (!evpkey) { + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_result_t ret = RNP_ERROR_GENERIC; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + int res; + res = secret ? EVP_PKEY_check(ctx) : EVP_PKEY_public_check(ctx); + if (res < 0) { + auto err = ERR_peek_last_error(); + RNP_LOG("EC key check failed: %lu (%s)", err, ERR_reason_error_string(err)); + } + if (res > 0) { + ret = RNP_SUCCESS; + } +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(evpkey); + return ret; +} + +bool +ec_write_pubkey(EVP_PKEY *pkey, pgp_mpi_t &mpi, pgp_curve_t curve) +{ + if (ec_is_raw_key(curve)) { + /* EdDSA and X25519 keys are saved in a different way */ + mpi.len = sizeof(mpi.mpi) - 1; + if (EVP_PKEY_get_raw_public_key(pkey, &mpi.mpi[1], &mpi.len) <= 0) { + RNP_LOG("Failed get raw public key: %lu", ERR_peek_last_error()); + return false; + } + assert(mpi.len == 32); + mpi.mpi[0] = 0x40; + mpi.len++; + return true; + } + const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); + if (!ec) { + RNP_LOG("Failed to retrieve EC key: %lu", ERR_peek_last_error()); + return false; + } + const EC_POINT *p = EC_KEY_get0_public_key(ec); + if (!p) { + RNP_LOG("Null point: %lu", ERR_peek_last_error()); + return false; + } + /* call below adds leading zeroes if needed */ + mpi.len = EC_POINT_point2oct( + EC_KEY_get0_group(ec), p, POINT_CONVERSION_UNCOMPRESSED, mpi.mpi, sizeof(mpi.mpi), NULL); + if (!mpi.len) { + RNP_LOG("Failed to encode public key: %lu", ERR_peek_last_error()); + } + return mpi.len; +} diff --git a/src/lib/crypto/ec_ossl.h b/src/lib/crypto/ec_ossl.h new file mode 100644 index 0000000..f16afe8 --- /dev/null +++ b/src/lib/crypto/ec_ossl.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef EC_OSSL_H_ +#define EC_OSSL_H_ + +#include "types.h" +#include "ec.h" +#include <openssl/evp.h> + +EVP_PKEY *ec_load_key(const pgp_mpi_t &keyp, const pgp_mpi_t *keyx, pgp_curve_t curve); + +rnp_result_t ec_validate_key(const pgp_ec_key_t &key, bool secret); + +EVP_PKEY *ec_generate_pkey(const pgp_pubkey_alg_t alg_id, const pgp_curve_t curve); + +bool ec_write_pubkey(EVP_PKEY *key, pgp_mpi_t &mpi, pgp_curve_t curve); + +#endif diff --git a/src/lib/crypto/ecdh.cpp b/src/lib/crypto/ecdh.cpp new file mode 100644 index 0000000..d4411c3 --- /dev/null +++ b/src/lib/crypto/ecdh.cpp @@ -0,0 +1,377 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <botan/ffi.h> +#include "hash_botan.hpp" +#include "ecdh.h" +#include "ecdh_utils.h" +#include "symmetric.h" +#include "types.h" +#include "utils.h" +#include "mem.h" +#include "bn.h" + +// Produces kek of size kek_len which corresponds to length of wrapping key +static bool +compute_kek(uint8_t * kek, + size_t kek_len, + const uint8_t * other_info, + size_t other_info_size, + const ec_curve_desc_t *curve_desc, + const pgp_mpi_t * ec_pubkey, + const botan_privkey_t ec_prvkey, + const pgp_hash_alg_t hash_alg) +{ + const uint8_t *p = ec_pubkey->mpi; + uint8_t p_len = ec_pubkey->len; + + if (curve_desc->rnp_curve_id == PGP_CURVE_25519) { + if ((p_len != 33) || (p[0] != 0x40)) { + return false; + } + p++; + p_len--; + } + + rnp::secure_array<uint8_t, MAX_CURVE_BYTELEN * 2 + 1> s; + + botan_pk_op_ka_t op_key_agreement = NULL; + bool ret = false; + char kdf_name[32] = {0}; + size_t s_len = s.size(); + + if (botan_pk_op_key_agreement_create(&op_key_agreement, ec_prvkey, "Raw", 0) || + botan_pk_op_key_agreement(op_key_agreement, s.data(), &s_len, p, p_len, NULL, 0)) { + goto end; + } + + snprintf( + kdf_name, sizeof(kdf_name), "SP800-56A(%s)", rnp::Hash_Botan::name_backend(hash_alg)); + ret = !botan_kdf( + kdf_name, kek, kek_len, s.data(), s_len, NULL, 0, other_info, other_info_size); +end: + return ret && !botan_pk_op_key_agreement_destroy(op_key_agreement); +} + +static bool +ecdh_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *key) +{ + bool res = false; + + const ec_curve_desc_t *curve = get_curve_desc(key->curve); + if (!curve) { + RNP_LOG("unknown curve"); + return false; + } + + if (curve->rnp_curve_id == PGP_CURVE_25519) { + if ((key->p.len != 33) || (key->p.mpi[0] != 0x40)) { + return false; + } + rnp::secure_array<uint8_t, 32> pkey; + memcpy(pkey.data(), key->p.mpi + 1, 32); + return !botan_pubkey_load_x25519(pubkey, pkey.data()); + } + + if (!mpi_bytes(&key->p) || (key->p.mpi[0] != 0x04)) { + RNP_LOG("Failed to load public key"); + return false; + } + + botan_mp_t px = NULL; + botan_mp_t py = NULL; + const size_t curve_order = BITS_TO_BYTES(curve->bitlen); + + if (botan_mp_init(&px) || botan_mp_init(&py) || + botan_mp_from_bin(px, &key->p.mpi[1], curve_order) || + botan_mp_from_bin(py, &key->p.mpi[1 + curve_order], curve_order)) { + goto end; + } + + if (!(res = !botan_pubkey_load_ecdh(pubkey, px, py, curve->botan_name))) { + RNP_LOG("failed to load ecdh public key"); + } +end: + botan_mp_destroy(px); + botan_mp_destroy(py); + return res; +} + +static bool +ecdh_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *key) +{ + const ec_curve_desc_t *curve = get_curve_desc(key->curve); + + if (!curve) { + return false; + } + + if (curve->rnp_curve_id == PGP_CURVE_25519) { + if (key->x.len != 32) { + RNP_LOG("wrong x25519 key"); + return false; + } + /* need to reverse byte order since in mpi we have big-endian */ + rnp::secure_array<uint8_t, 32> prkey; + for (int i = 0; i < 32; i++) { + prkey[i] = key->x.mpi[31 - i]; + } + return !botan_privkey_load_x25519(seckey, prkey.data()); + } + + bignum_t *x = NULL; + if (!(x = mpi2bn(&key->x))) { + return false; + } + bool res = !botan_privkey_load_ecdh(seckey, BN_HANDLE_PTR(x), curve->botan_name); + bn_free(x); + return res; +} + +rnp_result_t +ecdh_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + const ec_curve_desc_t *curve_desc = get_curve_desc(key->curve); + if (!curve_desc) { + return RNP_ERROR_NOT_SUPPORTED; + } + + if (!ecdh_load_public_key(&bpkey, key) || + botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + if (!ecdh_load_secret_key(&bskey, key) || + botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_privkey_destroy(bskey); + botan_pubkey_destroy(bpkey); + return ret; +} + +rnp_result_t +ecdh_encrypt_pkcs5(rnp::RNG * rng, + pgp_ecdh_encrypted_t * out, + const uint8_t *const in, + size_t in_len, + const pgp_ec_key_t * key, + const pgp_fingerprint_t &fingerprint) +{ + botan_privkey_t eph_prv_key = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + uint8_t other_info[MAX_SP800_56A_OTHER_INFO]; + uint8_t kek[32] = {0}; // Size of SHA-256 or smaller + // 'm' is padded to the 8-byte granularity + uint8_t m[MAX_SESSION_KEY_SIZE]; + const size_t m_padded_len = ((in_len / 8) + 1) * 8; + + if (!key || !out || !in || (in_len > sizeof(m))) { + return RNP_ERROR_BAD_PARAMETERS; + } +#if !defined(ENABLE_SM2) + if (key->curve == PGP_CURVE_SM2_P_256) { + RNP_LOG("SM2 curve support is disabled."); + return RNP_ERROR_NOT_IMPLEMENTED; + } +#endif + const ec_curve_desc_t *curve_desc = get_curve_desc(key->curve); + if (!curve_desc) { + RNP_LOG("unsupported curve"); + return RNP_ERROR_NOT_SUPPORTED; + } + + // +8 because of AES-wrap adds 8 bytes + if (ECDH_WRAPPED_KEY_SIZE < (m_padded_len + 8)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + // See 13.5 of RFC 4880 for definition of other_info_size + const size_t other_info_size = curve_desc->OIDhex_len + 46; + const size_t kek_len = pgp_key_size(key->key_wrap_alg); + size_t tmp_len = kdf_other_info_serialize( + other_info, curve_desc, fingerprint, key->kdf_hash_alg, key->key_wrap_alg); + + if (tmp_len != other_info_size) { + RNP_LOG("Serialization of other info failed"); + return RNP_ERROR_GENERIC; + } + + if (!strcmp(curve_desc->botan_name, "curve25519")) { + if (botan_privkey_create(&eph_prv_key, "Curve25519", "", rng->handle())) { + goto end; + } + } else { + if (botan_privkey_create( + &eph_prv_key, "ECDH", curve_desc->botan_name, rng->handle())) { + goto end; + } + } + + if (!compute_kek(kek, + kek_len, + other_info, + other_info_size, + curve_desc, + &key->p, + eph_prv_key, + key->kdf_hash_alg)) { + RNP_LOG("KEK computation failed"); + goto end; + } + + memcpy(m, in, in_len); + if (!pad_pkcs7(m, m_padded_len, in_len)) { + // Should never happen + goto end; + } + + out->mlen = sizeof(out->m); + if (botan_key_wrap3394(m, m_padded_len, kek, kek_len, out->m, &out->mlen)) { + goto end; + } + + /* we need to prepend 0x40 for the x25519 */ + if (key->curve == PGP_CURVE_25519) { + out->p.len = sizeof(out->p.mpi) - 1; + if (botan_pk_op_key_agreement_export_public( + eph_prv_key, out->p.mpi + 1, &out->p.len)) { + goto end; + } + out->p.mpi[0] = 0x40; + out->p.len++; + } else { + out->p.len = sizeof(out->p.mpi); + if (botan_pk_op_key_agreement_export_public(eph_prv_key, out->p.mpi, &out->p.len)) { + goto end; + } + } + + // All OK + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(eph_prv_key); + return ret; +} + +rnp_result_t +ecdh_decrypt_pkcs5(uint8_t * out, + size_t * out_len, + const pgp_ecdh_encrypted_t *in, + const pgp_ec_key_t * key, + const pgp_fingerprint_t & fingerprint) +{ + if (!out_len || !in || !key || !mpi_bytes(&key->x)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + const ec_curve_desc_t *curve_desc = get_curve_desc(key->curve); + if (!curve_desc) { + RNP_LOG("unknown curve"); + return RNP_ERROR_NOT_SUPPORTED; + } + + const pgp_symm_alg_t wrap_alg = key->key_wrap_alg; + const pgp_hash_alg_t kdf_hash = key->kdf_hash_alg; + /* Ensure that AES is used for wrapping */ + if ((wrap_alg != PGP_SA_AES_128) && (wrap_alg != PGP_SA_AES_192) && + (wrap_alg != PGP_SA_AES_256)) { + RNP_LOG("non-aes wrap algorithm"); + return RNP_ERROR_NOT_SUPPORTED; + } + + // See 13.5 of RFC 4880 for definition of other_info_size + uint8_t other_info[MAX_SP800_56A_OTHER_INFO]; + const size_t other_info_size = curve_desc->OIDhex_len + 46; + const size_t tmp_len = + kdf_other_info_serialize(other_info, curve_desc, fingerprint, kdf_hash, wrap_alg); + + if (other_info_size != tmp_len) { + RNP_LOG("Serialization of other info failed"); + return RNP_ERROR_GENERIC; + } + + botan_privkey_t prv_key = NULL; + if (!ecdh_load_secret_key(&prv_key, key)) { + RNP_LOG("failed to load ecdh secret key"); + return RNP_ERROR_GENERIC; + } + + // Size of SHA-256 or smaller + rnp::secure_array<uint8_t, MAX_SYMM_KEY_SIZE> kek; + rnp::secure_array<uint8_t, MAX_SESSION_KEY_SIZE> deckey; + + size_t deckey_len = deckey.size(); + size_t offset = 0; + rnp_result_t ret = RNP_ERROR_GENERIC; + + /* Security: Always return same error code in case compute_kek, + * botan_key_unwrap3394 or unpad_pkcs7 fails + */ + size_t kek_len = pgp_key_size(wrap_alg); + if (!compute_kek(kek.data(), + kek_len, + other_info, + other_info_size, + curve_desc, + &in->p, + prv_key, + kdf_hash)) { + goto end; + } + + if (botan_key_unwrap3394( + in->m, in->mlen, kek.data(), kek_len, deckey.data(), &deckey_len)) { + goto end; + } + + if (!unpad_pkcs7(deckey.data(), deckey_len, &offset)) { + goto end; + } + + if (*out_len < offset) { + ret = RNP_ERROR_SHORT_BUFFER; + goto end; + } + + *out_len = offset; + memcpy(out, deckey.data(), *out_len); + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(prv_key); + return ret; +} diff --git a/src/lib/crypto/ecdh.h b/src/lib/crypto/ecdh.h new file mode 100644 index 0000000..017e1e6 --- /dev/null +++ b/src/lib/crypto/ecdh.h @@ -0,0 +1,117 @@ +/*- + * Copyright (c) 2017 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ECDH_H_ +#define ECDH_H_ + +#include "crypto/ec.h" + +/* Max size of wrapped and obfuscated key size + * + * RNP pads a key with PKCS-5 always to 8 byte granularity, + * then 8 bytes is added by AES-wrap (RFC3394). + */ +#define ECDH_WRAPPED_KEY_SIZE 48 + +/* Forward declarations */ +typedef struct pgp_fingerprint_t pgp_fingerprint_t; + +typedef struct pgp_ecdh_encrypted_t { + pgp_mpi_t p; + uint8_t m[ECDH_WRAPPED_KEY_SIZE]; + size_t mlen; +} pgp_ecdh_encrypted_t; + +rnp_result_t ecdh_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret); + +/* + * @brief Sets hash algorithm and key wrapping algo + * based on curve_id + * + * @param key ec key to set parameters for + * @param curve underlying ECC curve ID + * + * @returns false if curve is not supported, otherwise true + */ +bool ecdh_set_params(pgp_ec_key_t *key, pgp_curve_t curve_id); + +/* + * Encrypts session key with a KEK agreed during ECDH as specified in + * RFC 4880 bis 01, 13.5 + * + * @param rng initialized rnp::RNG object + * @param session_key key to be encrypted + * @param session_key_len length of the key buffer + * @param wrapped_key [out] resulting key wrapped in by some AES + * as specified in RFC 3394 + * @param wrapped_key_len [out] length of the `wrapped_key' buffer + * Current implementation always produces 48 bytes as key + * is padded with PKCS-5/7 + * @param ephemeral_key [out] public ephemeral ECDH key used for key + * agreement (private part). Must be initialized + * @param pubkey public key to be used for encryption + * @param fingerprint fingerprint of the pubkey + * + * @return RNP_SUCCESS on success and output parameters are populated + * @return RNP_ERROR_NOT_SUPPORTED unknown curve + * @return RNP_ERROR_BAD_PARAMETERS unexpected input provided + * @return RNP_ERROR_SHORT_BUFFER `wrapped_key_len' to small to store result + * @return RNP_ERROR_GENERIC implementation error + */ +rnp_result_t ecdh_encrypt_pkcs5(rnp::RNG * rng, + pgp_ecdh_encrypted_t * out, + const uint8_t *const in, + size_t in_len, + const pgp_ec_key_t * key, + const pgp_fingerprint_t &fingerprint); + +/* + * Decrypts session key with a KEK agreed during ECDH as specified in + * RFC 4880 bis 01, 13.5 + * + * @param session_key [out] resulting session key + * @param session_key_len [out] length of the resulting session key + * @param wrapped_key session key wrapped with some AES as specified + * in RFC 3394 + * @param wrapped_key_len length of the `wrapped_key' buffer + * @param ephemeral_key public ephemeral ECDH key coming from + * encrypted packet. + * @param seckey secret key to be used for decryption + * @param fingerprint fingerprint of the key + * + * @return RNP_SUCCESS on success and output parameters are populated + * @return RNP_ERROR_NOT_SUPPORTED unknown curve + * @return RNP_ERROR_BAD_PARAMETERS unexpected input provided + * @return RNP_ERROR_SHORT_BUFFER `session_key_len' to small to store result + * @return RNP_ERROR_GENERIC decryption failed or implementation error + */ +rnp_result_t ecdh_decrypt_pkcs5(uint8_t * out, + size_t * out_len, + const pgp_ecdh_encrypted_t *in, + const pgp_ec_key_t * key, + const pgp_fingerprint_t & fingerprint); + +#endif // ECDH_H_ diff --git a/src/lib/crypto/ecdh_ossl.cpp b/src/lib/crypto/ecdh_ossl.cpp new file mode 100644 index 0000000..60b7260 --- /dev/null +++ b/src/lib/crypto/ecdh_ossl.cpp @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2021-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string> +#include <cassert> +#include "ecdh.h" +#include "ecdh_utils.h" +#include "ec_ossl.h" +#include "hash.hpp" +#include "symmetric.h" +#include "types.h" +#include "utils.h" +#include "logging.h" +#include "mem.h" +#include <openssl/evp.h> +#include <openssl/err.h> + +static const struct ecdh_wrap_alg_map_t { + pgp_symm_alg_t alg; + const char * name; +} ecdh_wrap_alg_map[] = {{PGP_SA_AES_128, "aes128-wrap"}, + {PGP_SA_AES_192, "aes192-wrap"}, + {PGP_SA_AES_256, "aes256-wrap"}}; + +rnp_result_t +ecdh_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + return ec_validate_key(*key, secret); +} + +static rnp_result_t +ecdh_derive_kek(uint8_t * x, + size_t xlen, + const pgp_ec_key_t & key, + const pgp_fingerprint_t &fingerprint, + uint8_t * kek, + const size_t kek_len) +{ + const ec_curve_desc_t *curve_desc = get_curve_desc(key.curve); + if (!curve_desc) { + RNP_LOG("unsupported curve"); + return RNP_ERROR_NOT_SUPPORTED; + } + + // Serialize other info, see 13.5 of RFC 4880 bis + uint8_t other_info[MAX_SP800_56A_OTHER_INFO]; + const size_t hash_len = rnp::Hash::size(key.kdf_hash_alg); + if (!hash_len) { + // must not assert here as kdf/hash algs are not checked during key parsing + RNP_LOG("Unsupported key wrap hash algorithm."); + return RNP_ERROR_NOT_SUPPORTED; + } + size_t other_len = kdf_other_info_serialize( + other_info, curve_desc, fingerprint, key.kdf_hash_alg, key.key_wrap_alg); + // Self-check + assert(other_len == curve_desc->OIDhex_len + 46); + // Derive KEK, using the KDF from SP800-56A + rnp::secure_array<uint8_t, PGP_MAX_HASH_SIZE> dgst; + assert(hash_len <= PGP_MAX_HASH_SIZE); + size_t reps = (kek_len + hash_len - 1) / hash_len; + // As we use AES & SHA2 we should not get more then 2 iterations + if (reps > 2) { + RNP_LOG("Invalid key wrap/hash alg combination."); + return RNP_ERROR_NOT_SUPPORTED; + } + size_t have = 0; + try { + for (size_t i = 1; i <= reps; i++) { + auto hash = rnp::Hash::create(key.kdf_hash_alg); + hash->add(i); + hash->add(x, xlen); + hash->add(other_info, other_len); + hash->finish(dgst.data()); + size_t bytes = std::min(hash_len, kek_len - have); + memcpy(kek + have, dgst.data(), bytes); + have += bytes; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("Failed to derive kek: %s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +static rnp_result_t +ecdh_rfc3394_wrap_ctx(EVP_CIPHER_CTX **ctx, + pgp_symm_alg_t wrap_alg, + const uint8_t * key, + bool decrypt) +{ + /* get OpenSSL EVP cipher for key wrap */ + const char *cipher_name = NULL; + ARRAY_LOOKUP_BY_ID(ecdh_wrap_alg_map, alg, name, wrap_alg, cipher_name); + if (!cipher_name) { + RNP_LOG("Unsupported key wrap algorithm: %d", (int) wrap_alg); + return RNP_ERROR_NOT_SUPPORTED; + } + const EVP_CIPHER *cipher = EVP_get_cipherbyname(cipher_name); + if (!cipher) { + RNP_LOG("Cipher %s is not supported by OpenSSL.", cipher_name); + return RNP_ERROR_NOT_SUPPORTED; + } + *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + RNP_LOG("Context allocation failed : %lu", ERR_peek_last_error()); + return RNP_ERROR_OUT_OF_MEMORY; + } + EVP_CIPHER_CTX_set_flags(*ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + int res = decrypt ? EVP_DecryptInit_ex(*ctx, cipher, NULL, key, NULL) : + EVP_EncryptInit_ex(*ctx, cipher, NULL, key, NULL); + if (res <= 0) { + RNP_LOG("Failed to initialize cipher : %lu", ERR_peek_last_error()); + EVP_CIPHER_CTX_free(*ctx); + *ctx = NULL; + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} + +static rnp_result_t +ecdh_rfc3394_wrap(uint8_t * out, + size_t * out_len, + const uint8_t *const in, + size_t in_len, + const uint8_t * key, + pgp_symm_alg_t wrap_alg) +{ + EVP_CIPHER_CTX *ctx = NULL; + rnp_result_t ret = ecdh_rfc3394_wrap_ctx(&ctx, wrap_alg, key, false); + if (ret) { + RNP_LOG("Wrap context initialization failed."); + return ret; + } + int intlen = *out_len; + /* encrypts in one pass, no final is needed */ + int res = EVP_EncryptUpdate(ctx, out, &intlen, in, in_len); + if (res <= 0) { + RNP_LOG("Failed to encrypt data : %lu", ERR_peek_last_error()); + } else { + *out_len = intlen; + } + EVP_CIPHER_CTX_free(ctx); + return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; +} + +static rnp_result_t +ecdh_rfc3394_unwrap(uint8_t * out, + size_t * out_len, + const uint8_t *const in, + size_t in_len, + const uint8_t * key, + pgp_symm_alg_t wrap_alg) +{ + if ((in_len < 16) || (in_len % 8)) { + RNP_LOG("Invalid wrapped key size."); + return RNP_ERROR_GENERIC; + } + EVP_CIPHER_CTX *ctx = NULL; + rnp_result_t ret = ecdh_rfc3394_wrap_ctx(&ctx, wrap_alg, key, true); + if (ret) { + RNP_LOG("Unwrap context initialization failed."); + return ret; + } + int intlen = *out_len; + /* decrypts in one pass, no final is needed */ + int res = EVP_DecryptUpdate(ctx, out, &intlen, in, in_len); + if (res <= 0) { + RNP_LOG("Failed to decrypt data : %lu", ERR_peek_last_error()); + } else { + *out_len = intlen; + } + EVP_CIPHER_CTX_free(ctx); + return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; +} + +static bool +ecdh_derive_secret(EVP_PKEY *sec, EVP_PKEY *peer, uint8_t *x, size_t *xlen) +{ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(sec, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + return false; + } + bool res = false; + if (EVP_PKEY_derive_init(ctx) <= 0) { + RNP_LOG("Key derivation init failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_derive_set_peer(ctx, peer) <= 0) { + RNP_LOG("Peer setting failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_derive(ctx, x, xlen) <= 0) { + RNP_LOG("Failed to obtain shared secret size: %lu", ERR_peek_last_error()); + goto done; + } + res = true; +done: + EVP_PKEY_CTX_free(ctx); + return res; +} + +static size_t +ecdh_kek_len(pgp_symm_alg_t wrap_alg) +{ + switch (wrap_alg) { + case PGP_SA_AES_128: + case PGP_SA_AES_192: + case PGP_SA_AES_256: + return pgp_key_size(wrap_alg); + default: + return 0; + } +} + +rnp_result_t +ecdh_encrypt_pkcs5(rnp::RNG * rng, + pgp_ecdh_encrypted_t * out, + const uint8_t *const in, + size_t in_len, + const pgp_ec_key_t * key, + const pgp_fingerprint_t &fingerprint) +{ + if (!key || !out || !in || (in_len > MAX_SESSION_KEY_SIZE)) { + return RNP_ERROR_BAD_PARAMETERS; + } +#if !defined(ENABLE_SM2) + if (key->curve == PGP_CURVE_SM2_P_256) { + RNP_LOG("SM2 curve support is disabled."); + return RNP_ERROR_NOT_IMPLEMENTED; + } +#endif + /* check whether we have valid wrap_alg before doing heavy operations */ + size_t keklen = ecdh_kek_len(key->key_wrap_alg); + if (!keklen) { + RNP_LOG("Unsupported key wrap algorithm: %d", (int) key->key_wrap_alg); + return RNP_ERROR_NOT_SUPPORTED; + } + /* load our public key */ + EVP_PKEY *pkey = ec_load_key(key->p, NULL, key->curve); + if (!pkey) { + RNP_LOG("Failed to load public key."); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp::secure_array<uint8_t, MAX_CURVE_BYTELEN + 1> sec; + rnp::secure_array<uint8_t, MAX_AES_KEY_SIZE> kek; + rnp::secure_array<uint8_t, MAX_SESSION_KEY_SIZE> mpad; + + size_t seclen = sec.size(); + rnp_result_t ret = RNP_ERROR_GENERIC; + /* generate ephemeral key */ + EVP_PKEY *ephkey = ec_generate_pkey(PGP_PKA_ECDH, key->curve); + if (!ephkey) { + RNP_LOG("Failed to generate ephemeral key."); + ret = RNP_ERROR_KEY_GENERATION; + goto done; + } + /* do ECDH derivation */ + if (!ecdh_derive_secret(ephkey, pkey, sec.data(), &seclen)) { + RNP_LOG("ECDH derivation failed."); + goto done; + } + /* here we got x value in sec, deriving kek */ + ret = ecdh_derive_kek(sec.data(), seclen, *key, fingerprint, kek.data(), keklen); + if (ret) { + RNP_LOG("Failed to derive KEK."); + goto done; + } + /* add PKCS#7 padding */ + size_t m_padded_len; + m_padded_len = ((in_len / 8) + 1) * 8; + memcpy(mpad.data(), in, in_len); + if (!pad_pkcs7(mpad.data(), m_padded_len, in_len)) { + RNP_LOG("Failed to add PKCS #7 padding."); + goto done; + } + /* do RFC 3394 AES key wrap */ + static_assert(sizeof(out->m) == ECDH_WRAPPED_KEY_SIZE, "Wrong ECDH wrapped key size."); + out->mlen = ECDH_WRAPPED_KEY_SIZE; + ret = ecdh_rfc3394_wrap( + out->m, &out->mlen, mpad.data(), m_padded_len, kek.data(), key->key_wrap_alg); + if (ret) { + RNP_LOG("Failed to wrap key."); + goto done; + } + /* write ephemeral public key */ + if (!ec_write_pubkey(ephkey, out->p, key->curve)) { + RNP_LOG("Failed to write ec key."); + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_free(ephkey); + EVP_PKEY_free(pkey); + return ret; +} + +rnp_result_t +ecdh_decrypt_pkcs5(uint8_t * out, + size_t * out_len, + const pgp_ecdh_encrypted_t *in, + const pgp_ec_key_t * key, + const pgp_fingerprint_t & fingerprint) +{ + if (!out || !out_len || !in || !key || !mpi_bytes(&key->x)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* check whether we have valid wrap_alg before doing heavy operations */ + size_t keklen = ecdh_kek_len(key->key_wrap_alg); + if (!keklen) { + RNP_LOG("Unsupported key wrap algorithm: %d", (int) key->key_wrap_alg); + return RNP_ERROR_NOT_SUPPORTED; + } + /* load ephemeral public key */ + EVP_PKEY *ephkey = ec_load_key(in->p, NULL, key->curve); + if (!ephkey) { + RNP_LOG("Failed to load ephemeral public key."); + return RNP_ERROR_BAD_PARAMETERS; + } + /* load our secret key */ + rnp::secure_array<uint8_t, MAX_CURVE_BYTELEN + 1> sec; + rnp::secure_array<uint8_t, MAX_AES_KEY_SIZE> kek; + rnp::secure_array<uint8_t, MAX_SESSION_KEY_SIZE> mpad; + + size_t seclen = sec.size(); + size_t mpadlen = mpad.size(); + rnp_result_t ret = RNP_ERROR_GENERIC; + EVP_PKEY * pkey = ec_load_key(key->p, &key->x, key->curve); + if (!pkey) { + RNP_LOG("Failed to load secret key."); + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + /* do ECDH derivation */ + if (!ecdh_derive_secret(pkey, ephkey, sec.data(), &seclen)) { + RNP_LOG("ECDH derivation failed."); + goto done; + } + /* here we got x value in sec, deriving kek */ + ret = ecdh_derive_kek(sec.data(), seclen, *key, fingerprint, kek.data(), keklen); + if (ret) { + RNP_LOG("Failed to derive KEK."); + goto done; + } + /* do RFC 3394 AES key unwrap */ + ret = ecdh_rfc3394_unwrap( + mpad.data(), &mpadlen, in->m, in->mlen, kek.data(), key->key_wrap_alg); + if (ret) { + RNP_LOG("Failed to unwrap key."); + goto done; + } + /* remove PKCS#7 padding */ + if (!unpad_pkcs7(mpad.data(), mpadlen, &mpadlen)) { + RNP_LOG("Failed to unpad key."); + goto done; + } + assert(mpadlen <= *out_len); + *out_len = mpadlen; + memcpy(out, mpad.data(), mpadlen); + ret = RNP_SUCCESS; +done: + EVP_PKEY_free(ephkey); + EVP_PKEY_free(pkey); + return ret; +} diff --git a/src/lib/crypto/ecdh_utils.cpp b/src/lib/crypto/ecdh_utils.cpp new file mode 100644 index 0000000..3ceb153 --- /dev/null +++ b/src/lib/crypto/ecdh_utils.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ecdh_utils.h" +#include "types.h" +#include "utils.h" +#include <cassert> + +/* Used by ECDH keys. Specifies which hash and wrapping algorithm + * to be used (see point 15. of RFC 4880). + * + * Note: sync with ec_curves. + */ +static const struct ecdh_params_t { + pgp_curve_t curve; /* Curve ID */ + pgp_hash_alg_t hash; /* Hash used by kdf */ + pgp_symm_alg_t wrap_alg; /* Symmetric algorithm used to wrap KEK*/ +} ecdh_params[] = { + {PGP_CURVE_NIST_P_256, PGP_HASH_SHA256, PGP_SA_AES_128}, + {PGP_CURVE_NIST_P_384, PGP_HASH_SHA384, PGP_SA_AES_192}, + {PGP_CURVE_NIST_P_521, PGP_HASH_SHA512, PGP_SA_AES_256}, + {PGP_CURVE_BP256, PGP_HASH_SHA256, PGP_SA_AES_128}, + {PGP_CURVE_BP384, PGP_HASH_SHA384, PGP_SA_AES_192}, + {PGP_CURVE_BP512, PGP_HASH_SHA512, PGP_SA_AES_256}, + {PGP_CURVE_25519, PGP_HASH_SHA256, PGP_SA_AES_128}, + {PGP_CURVE_P256K1, PGP_HASH_SHA256, PGP_SA_AES_128}, +}; + +// "Anonymous Sender " in hex +static const unsigned char ANONYMOUS_SENDER[] = {0x41, 0x6E, 0x6F, 0x6E, 0x79, 0x6D, 0x6F, + 0x75, 0x73, 0x20, 0x53, 0x65, 0x6E, 0x64, + 0x65, 0x72, 0x20, 0x20, 0x20, 0x20}; + +// returns size of data written to other_info +size_t +kdf_other_info_serialize(uint8_t other_info[MAX_SP800_56A_OTHER_INFO], + const ec_curve_desc_t * ec_curve, + const pgp_fingerprint_t &fingerprint, + const pgp_hash_alg_t kdf_hash, + const pgp_symm_alg_t wrap_alg) +{ + assert(fingerprint.length >= 20); + uint8_t *buf_ptr = &other_info[0]; + + /* KDF-OtherInfo: AlgorithmID + * Current implementation will always use SHA-512 and AES-256 for KEK wrapping + */ + *(buf_ptr++) = ec_curve->OIDhex_len; + memcpy(buf_ptr, ec_curve->OIDhex, ec_curve->OIDhex_len); + buf_ptr += ec_curve->OIDhex_len; + *(buf_ptr++) = PGP_PKA_ECDH; + // size of following 3 params (each 1 byte) + *(buf_ptr++) = 0x03; + // Value reserved for future use + *(buf_ptr++) = 0x01; + // Hash used with KDF + *(buf_ptr++) = kdf_hash; + // Algorithm ID used for key wrapping + *(buf_ptr++) = wrap_alg; + + /* KDF-OtherInfo: PartyUInfo + * 20 bytes representing "Anonymous Sender " + */ + memcpy(buf_ptr, ANONYMOUS_SENDER, sizeof(ANONYMOUS_SENDER)); + buf_ptr += sizeof(ANONYMOUS_SENDER); + + // keep 20, as per spec + memcpy(buf_ptr, fingerprint.fingerprint, 20); + return (buf_ptr - other_info) + 20 /*anonymous_sender*/; +} + +bool +pad_pkcs7(uint8_t *buf, size_t buf_len, size_t offset) +{ + if (buf_len <= offset) { + // Must have at least 1 byte of padding + return false; + } + + const uint8_t pad_byte = buf_len - offset; + memset(buf + offset, pad_byte, pad_byte); + return true; +} + +bool +unpad_pkcs7(uint8_t *buf, size_t buf_len, size_t *offset) +{ + if (!buf || !offset || !buf_len) { + return false; + } + + uint8_t err = 0; + const uint8_t pad_byte = buf[buf_len - 1]; + const uint32_t pad_begin = buf_len - pad_byte; + + // TODO: Still >, <, and <=,== are not constant time (maybe?) + err |= (pad_byte > buf_len); + err |= (pad_byte == 0); + + /* Check if padding is OK */ + for (size_t c = 0; c < buf_len; c++) { + err |= (buf[c] ^ pad_byte) * (pad_begin <= c); + } + + *offset = pad_begin; + return (err == 0); +} + +bool +ecdh_set_params(pgp_ec_key_t *key, pgp_curve_t curve_id) +{ + for (size_t i = 0; i < ARRAY_SIZE(ecdh_params); i++) { + if (ecdh_params[i].curve == curve_id) { + key->kdf_hash_alg = ecdh_params[i].hash; + key->key_wrap_alg = ecdh_params[i].wrap_alg; + return true; + } + } + + return false; +} + +bool +x25519_tweak_bits(pgp_ec_key_t &key) +{ + if (key.x.len != 32) { + return false; + } + /* MPI is big-endian, while raw x25519 key is little-endian */ + key.x.mpi[31] &= 248; // zero 3 low bits + key.x.mpi[0] &= 127; // zero high bit + key.x.mpi[0] |= 64; // set high - 1 bit + return true; +} + +bool +x25519_bits_tweaked(const pgp_ec_key_t &key) +{ + if (key.x.len != 32) { + return false; + } + return !(key.x.mpi[31] & 7) && (key.x.mpi[0] < 128) && (key.x.mpi[0] >= 64); +} diff --git a/src/lib/crypto/ecdh_utils.h b/src/lib/crypto/ecdh_utils.h new file mode 100644 index 0000000..2d37a71 --- /dev/null +++ b/src/lib/crypto/ecdh_utils.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ECDH_UTILS_H_ +#define ECDH_UTILS_H_ + +#include "ecdh.h" + +#define MAX_SP800_56A_OTHER_INFO 56 +// Keys up to 312 bits (+1 bytes of PKCS5 padding) +#define MAX_SESSION_KEY_SIZE 40 +#define MAX_AES_KEY_SIZE 32 + +size_t kdf_other_info_serialize(uint8_t other_info[MAX_SP800_56A_OTHER_INFO], + const ec_curve_desc_t * ec_curve, + const pgp_fingerprint_t &fingerprint, + const pgp_hash_alg_t kdf_hash, + const pgp_symm_alg_t wrap_alg); + +bool pad_pkcs7(uint8_t *buf, size_t buf_len, size_t offset); + +bool unpad_pkcs7(uint8_t *buf, size_t buf_len, size_t *offset); + +#endif // ECDH_UTILS_H_ diff --git a/src/lib/crypto/ecdsa.cpp b/src/lib/crypto/ecdsa.cpp new file mode 100644 index 0000000..cffce12 --- /dev/null +++ b/src/lib/crypto/ecdsa.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ecdsa.h" +#include "utils.h" +#include <botan/ffi.h> +#include <string.h> +#include "bn.h" + +static bool +ecdsa_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *keydata) +{ + botan_mp_t px = NULL; + botan_mp_t py = NULL; + bool res = false; + + const ec_curve_desc_t *curve = get_curve_desc(keydata->curve); + if (!curve) { + RNP_LOG("unknown curve"); + return false; + } + const size_t curve_order = BITS_TO_BYTES(curve->bitlen); + + if (!mpi_bytes(&keydata->p) || (keydata->p.mpi[0] != 0x04)) { + RNP_LOG( + "Failed to load public key: %zu, %02x", mpi_bytes(&keydata->p), keydata->p.mpi[0]); + return false; + } + + if (botan_mp_init(&px) || botan_mp_init(&py) || + botan_mp_from_bin(px, &keydata->p.mpi[1], curve_order) || + botan_mp_from_bin(py, &keydata->p.mpi[1 + curve_order], curve_order)) { + goto end; + } + + if (!(res = !botan_pubkey_load_ecdsa(pubkey, px, py, curve->botan_name))) { + RNP_LOG("failed to load ecdsa public key"); + } +end: + botan_mp_destroy(px); + botan_mp_destroy(py); + return res; +} + +static bool +ecdsa_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *keydata) +{ + const ec_curve_desc_t *curve; + bignum_t * x = NULL; + bool res = false; + + if (!(curve = get_curve_desc(keydata->curve))) { + return false; + } + if (!(x = mpi2bn(&keydata->x))) { + return false; + } + if (!(res = !botan_privkey_load_ecdsa(seckey, BN_HANDLE_PTR(x), curve->botan_name))) { + RNP_LOG("Can't load private key"); + } + bn_free(x); + return res; +} + +rnp_result_t +ecdsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + if (!ecdsa_load_public_key(&bpkey, key) || + botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + if (!ecdsa_load_secret_key(&bskey, key) || + botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_privkey_destroy(bskey); + botan_pubkey_destroy(bpkey); + return ret; +} + +static const char * +ecdsa_padding_str_for(pgp_hash_alg_t hash_alg) +{ + switch (hash_alg) { + case PGP_HASH_MD5: + return "Raw(MD5)"; + case PGP_HASH_SHA1: + return "Raw(SHA-1)"; + case PGP_HASH_RIPEMD: + return "Raw(RIPEMD-160)"; + + case PGP_HASH_SHA256: + return "Raw(SHA-256)"; + case PGP_HASH_SHA384: + return "Raw(SHA-384)"; + case PGP_HASH_SHA512: + return "Raw(SHA-512)"; + case PGP_HASH_SHA224: + return "Raw(SHA-224)"; + case PGP_HASH_SHA3_256: + return "Raw(SHA3(256))"; + case PGP_HASH_SHA3_512: + return "Raw(SHA3(512))"; + + case PGP_HASH_SM3: + return "Raw(SM3)"; + default: + return "Raw"; + } +} + +rnp_result_t +ecdsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + botan_pk_op_sign_t signer = NULL; + botan_privkey_t b_key = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + uint8_t out_buf[2 * MAX_CURVE_BYTELEN] = {0}; + const ec_curve_desc_t *curve = get_curve_desc(key->curve); + const char * padding_str = ecdsa_padding_str_for(hash_alg); + + if (!curve) { + return RNP_ERROR_BAD_PARAMETERS; + } + const size_t curve_order = BITS_TO_BYTES(curve->bitlen); + size_t sig_len = 2 * curve_order; + + if (!ecdsa_load_secret_key(&b_key, key)) { + RNP_LOG("Can't load private key"); + goto end; + } + + if (botan_pk_op_sign_create(&signer, b_key, padding_str, 0)) { + goto end; + } + + if (botan_pk_op_sign_update(signer, hash, hash_len)) { + goto end; + } + + if (botan_pk_op_sign_finish(signer, rng->handle(), out_buf, &sig_len)) { + RNP_LOG("Signing failed"); + goto end; + } + + // Allocate memory and copy results + if (mem2mpi(&sig->r, out_buf, curve_order) && + mem2mpi(&sig->s, out_buf + curve_order, curve_order)) { + ret = RNP_SUCCESS; + } +end: + botan_privkey_destroy(b_key); + botan_pk_op_sign_destroy(signer); + return ret; +} + +rnp_result_t +ecdsa_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + botan_pubkey_t pub = NULL; + botan_pk_op_verify_t verifier = NULL; + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + uint8_t sign_buf[2 * MAX_CURVE_BYTELEN] = {0}; + size_t r_blen, s_blen; + const char * padding_str = ecdsa_padding_str_for(hash_alg); + + const ec_curve_desc_t *curve = get_curve_desc(key->curve); + if (!curve) { + RNP_LOG("unknown curve"); + return RNP_ERROR_BAD_PARAMETERS; + } + const size_t curve_order = BITS_TO_BYTES(curve->bitlen); + + if (!ecdsa_load_public_key(&pub, key)) { + goto end; + } + + if (botan_pk_op_verify_create(&verifier, pub, padding_str, 0)) { + goto end; + } + + if (botan_pk_op_verify_update(verifier, hash, hash_len)) { + goto end; + } + + r_blen = mpi_bytes(&sig->r); + s_blen = mpi_bytes(&sig->s); + if ((r_blen > curve_order) || (s_blen > curve_order) || + (curve_order > MAX_CURVE_BYTELEN)) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + + // Both can't fail + mpi2mem(&sig->r, &sign_buf[curve_order - r_blen]); + mpi2mem(&sig->s, &sign_buf[curve_order + curve_order - s_blen]); + + if (!botan_pk_op_verify_finish(verifier, sign_buf, curve_order * 2)) { + ret = RNP_SUCCESS; + } +end: + botan_pubkey_destroy(pub); + botan_pk_op_verify_destroy(verifier); + return ret; +} + +pgp_hash_alg_t +ecdsa_get_min_hash(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_NIST_P_256: + case PGP_CURVE_BP256: + case PGP_CURVE_P256K1: + return PGP_HASH_SHA256; + case PGP_CURVE_NIST_P_384: + case PGP_CURVE_BP384: + return PGP_HASH_SHA384; + case PGP_CURVE_NIST_P_521: + case PGP_CURVE_BP512: + return PGP_HASH_SHA512; + default: + return PGP_HASH_UNKNOWN; + } +} diff --git a/src/lib/crypto/ecdsa.h b/src/lib/crypto/ecdsa.h new file mode 100644 index 0000000..aebfe6a --- /dev/null +++ b/src/lib/crypto/ecdsa.h @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2017 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ECDSA_H_ +#define ECDSA_H_ + +#include "crypto/ec.h" + +rnp_result_t ecdsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret); + +rnp_result_t ecdsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key); + +rnp_result_t ecdsa_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key); + +/* + * @brief Returns hash which should be used with the curve + * + * @param curve Curve ID + * + * @returns Either ID of the hash algorithm, or PGP_HASH_UNKNOWN + * if not found + */ +pgp_hash_alg_t ecdsa_get_min_hash(pgp_curve_t curve); + +#endif // ECDSA_H_ diff --git a/src/lib/crypto/ecdsa_ossl.cpp b/src/lib/crypto/ecdsa_ossl.cpp new file mode 100644 index 0000000..534811a --- /dev/null +++ b/src/lib/crypto/ecdsa_ossl.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ecdsa.h" +#include "utils.h" +#include <string.h> +#include "bn.h" +#include "ec_ossl.h" +#include <openssl/evp.h> +#include <openssl/err.h> +#include <openssl/ec.h> + +static bool +ecdsa_decode_sig(const uint8_t *data, size_t len, pgp_ec_signature_t &sig) +{ + ECDSA_SIG *esig = d2i_ECDSA_SIG(NULL, &data, len); + if (!esig) { + RNP_LOG("Failed to parse ECDSA sig: %lu", ERR_peek_last_error()); + return false; + } + const BIGNUM *r, *s; + ECDSA_SIG_get0(esig, &r, &s); + bn2mpi(r, &sig.r); + bn2mpi(s, &sig.s); + ECDSA_SIG_free(esig); + return true; +} + +static bool +ecdsa_encode_sig(uint8_t *data, size_t *len, const pgp_ec_signature_t &sig) +{ + bool res = false; + ECDSA_SIG *dsig = ECDSA_SIG_new(); + BIGNUM * r = mpi2bn(&sig.r); + BIGNUM * s = mpi2bn(&sig.s); + if (!dsig || !r || !s) { + RNP_LOG("Allocation failed."); + goto done; + } + ECDSA_SIG_set0(dsig, r, s); + r = NULL; + s = NULL; + int outlen; + outlen = i2d_ECDSA_SIG(dsig, &data); + if (outlen < 0) { + RNP_LOG("Failed to encode signature."); + goto done; + } + *len = outlen; + res = true; +done: + ECDSA_SIG_free(dsig); + BN_free(r); + BN_free(s); + return res; +} + +rnp_result_t +ecdsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + return ec_validate_key(*key, secret); +} + +rnp_result_t +ecdsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + if (mpi_bytes(&key->x) == 0) { + RNP_LOG("private key not set"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* Load secret key to DSA structure*/ + EVP_PKEY *evpkey = ec_load_key(key->p, &key->x, key->curve); + if (!evpkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + /* init context and sign */ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_sign_init(ctx) <= 0) { + RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error()); + goto done; + } + sig->s.len = PGP_MPINT_SIZE; + if (EVP_PKEY_sign(ctx, sig->s.mpi, &sig->s.len, hash, hash_len) <= 0) { + RNP_LOG("Signing failed: %lu", ERR_peek_last_error()); + sig->s.len = 0; + goto done; + } + if (!ecdsa_decode_sig(&sig->s.mpi[0], sig->s.len, *sig)) { + RNP_LOG("Failed to parse ECDSA sig: %lu", ERR_peek_last_error()); + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(evpkey); + return ret; +} + +rnp_result_t +ecdsa_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + /* Load secret key to DSA structure*/ + EVP_PKEY *evpkey = ec_load_key(key->p, NULL, key->curve); + if (!evpkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + /* init context and sign */ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_verify_init(ctx) <= 0) { + RNP_LOG("Failed to initialize verify: %lu", ERR_peek_last_error()); + goto done; + } + pgp_mpi_t sigbuf; + if (!ecdsa_encode_sig(sigbuf.mpi, &sigbuf.len, *sig)) { + goto done; + } + if (EVP_PKEY_verify(ctx, sigbuf.mpi, sigbuf.len, hash, hash_len) > 0) { + ret = RNP_SUCCESS; + } +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(evpkey); + return ret; +} + +pgp_hash_alg_t +ecdsa_get_min_hash(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_NIST_P_256: + case PGP_CURVE_BP256: + case PGP_CURVE_P256K1: + return PGP_HASH_SHA256; + case PGP_CURVE_NIST_P_384: + case PGP_CURVE_BP384: + return PGP_HASH_SHA384; + case PGP_CURVE_NIST_P_521: + case PGP_CURVE_BP512: + return PGP_HASH_SHA512; + default: + return PGP_HASH_UNKNOWN; + } +} diff --git a/src/lib/crypto/eddsa.cpp b/src/lib/crypto/eddsa.cpp new file mode 100644 index 0000000..8669180 --- /dev/null +++ b/src/lib/crypto/eddsa.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <botan/ffi.h> +#include "eddsa.h" +#include "utils.h" + +static bool +eddsa_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *keydata) +{ + if (keydata->curve != PGP_CURVE_ED25519) { + return false; + } + /* + * See draft-ietf-openpgp-rfc4880bis-01 section 13.3 + */ + if ((mpi_bytes(&keydata->p) != 33) || (keydata->p.mpi[0] != 0x40)) { + return false; + } + if (botan_pubkey_load_ed25519(pubkey, keydata->p.mpi + 1)) { + return false; + } + + return true; +} + +static bool +eddsa_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *keydata) +{ + uint8_t keybuf[32] = {0}; + size_t sz; + + if (keydata->curve != PGP_CURVE_ED25519) { + return false; + } + sz = mpi_bytes(&keydata->x); + if (!sz || (sz > 32)) { + return false; + } + mpi2mem(&keydata->x, keybuf + 32 - sz); + if (botan_privkey_load_ed25519(seckey, keybuf)) { + return false; + } + + return true; +} + +rnp_result_t +eddsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + if (!eddsa_load_public_key(&bpkey, key) || + botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + if (!eddsa_load_secret_key(&bskey, key) || + botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_privkey_destroy(bskey); + botan_pubkey_destroy(bpkey); + return ret; +} + +rnp_result_t +eddsa_generate(rnp::RNG *rng, pgp_ec_key_t *key) +{ + botan_privkey_t eddsa = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + uint8_t key_bits[64]; + + if (botan_privkey_create(&eddsa, "Ed25519", NULL, rng->handle()) != 0) { + goto end; + } + + if (botan_privkey_ed25519_get_privkey(eddsa, key_bits)) { + goto end; + } + + // First 32 bytes of key_bits are the EdDSA seed (private key) + // Second 32 bytes are the EdDSA public key + + mem2mpi(&key->x, key_bits, 32); + // insert the required 0x40 prefix on the public key + key_bits[31] = 0x40; + mem2mpi(&key->p, key_bits + 31, 33); + key->curve = PGP_CURVE_ED25519; + + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(eddsa); + return ret; +} + +rnp_result_t +eddsa_verify(const pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + botan_pubkey_t eddsa = NULL; + botan_pk_op_verify_t verify_op = NULL; + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + uint8_t bn_buf[64] = {0}; + + if (!eddsa_load_public_key(&eddsa, key)) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + + if (botan_pk_op_verify_create(&verify_op, eddsa, "Pure", 0) != 0) { + goto done; + } + + if (botan_pk_op_verify_update(verify_op, hash, hash_len) != 0) { + goto done; + } + + // Unexpected size for Ed25519 signature + if ((mpi_bytes(&sig->r) > 32) || (mpi_bytes(&sig->s) > 32)) { + goto done; + } + mpi2mem(&sig->r, &bn_buf[32 - mpi_bytes(&sig->r)]); + mpi2mem(&sig->s, &bn_buf[64 - mpi_bytes(&sig->s)]); + + if (botan_pk_op_verify_finish(verify_op, bn_buf, 64) == 0) { + ret = RNP_SUCCESS; + } +done: + botan_pk_op_verify_destroy(verify_op); + botan_pubkey_destroy(eddsa); + return ret; +} + +rnp_result_t +eddsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + botan_privkey_t eddsa = NULL; + botan_pk_op_sign_t sign_op = NULL; + rnp_result_t ret = RNP_ERROR_SIGNING_FAILED; + uint8_t bn_buf[64] = {0}; + size_t sig_size = sizeof(bn_buf); + + if (!eddsa_load_secret_key(&eddsa, key)) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + + if (botan_pk_op_sign_create(&sign_op, eddsa, "Pure", 0) != 0) { + goto done; + } + + if (botan_pk_op_sign_update(sign_op, hash, hash_len) != 0) { + goto done; + } + + if (botan_pk_op_sign_finish(sign_op, rng->handle(), bn_buf, &sig_size) != 0) { + goto done; + } + + // Unexpected size... + if (sig_size != 64) { + goto done; + } + + mem2mpi(&sig->r, bn_buf, 32); + mem2mpi(&sig->s, bn_buf + 32, 32); + ret = RNP_SUCCESS; +done: + botan_pk_op_sign_destroy(sign_op); + botan_privkey_destroy(eddsa); + return ret; +} diff --git a/src/lib/crypto/eddsa.h b/src/lib/crypto/eddsa.h new file mode 100644 index 0000000..7410a28 --- /dev/null +++ b/src/lib/crypto/eddsa.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_ED25519_H_ +#define RNP_ED25519_H_ + +#include "ec.h" + +rnp_result_t eddsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret); +/* + * curve_len must be 255 currently (for Ed25519) + * If Ed448 was supported in the future curve_len=448 would also be allowed. + */ +rnp_result_t eddsa_generate(rnp::RNG *rng, pgp_ec_key_t *key); + +rnp_result_t eddsa_verify(const pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key); + +rnp_result_t eddsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key); + +#endif diff --git a/src/lib/crypto/eddsa_ossl.cpp b/src/lib/crypto/eddsa_ossl.cpp new file mode 100644 index 0000000..16d8fad --- /dev/null +++ b/src/lib/crypto/eddsa_ossl.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string> +#include <cassert> +#include "eddsa.h" +#include "ec.h" +#include "ec_ossl.h" +#include "utils.h" +#include "bn.h" +#include <openssl/evp.h> +#include <openssl/objects.h> +#include <openssl/err.h> +#include <openssl/ec.h> + +rnp_result_t +eddsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + /* Not implemented in the OpenSSL, so just do basic size checks. */ + if ((mpi_bytes(&key->p) != 33) || (key->p.mpi[0] != 0x40)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (secret && mpi_bytes(&key->x) > 32) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} + +rnp_result_t +eddsa_generate(rnp::RNG *rng, pgp_ec_key_t *key) +{ + rnp_result_t ret = ec_generate(rng, key, PGP_PKA_EDDSA, PGP_CURVE_ED25519); + if (!ret) { + key->curve = PGP_CURVE_ED25519; + } + return ret; +} + +rnp_result_t +eddsa_verify(const pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + if ((mpi_bytes(&sig->r) > 32) || (mpi_bytes(&sig->s) > 32)) { + RNP_LOG("Invalid EdDSA signature."); + return RNP_ERROR_BAD_PARAMETERS; + } + if ((mpi_bytes(&key->p) != 33) || (key->p.mpi[0] != 0x40)) { + RNP_LOG("Invalid EdDSA public key."); + return RNP_ERROR_BAD_PARAMETERS; + } + + EVP_PKEY *evpkey = ec_load_key(key->p, NULL, PGP_CURVE_ED25519); + if (!evpkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + uint8_t sigbuf[64] = {0}; + /* init context and sign */ + EVP_PKEY_CTX *ctx = NULL; + EVP_MD_CTX * md = EVP_MD_CTX_new(); + if (!md) { + RNP_LOG("Failed to allocate MD ctx: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_DigestVerifyInit(md, &ctx, NULL, NULL, evpkey) <= 0) { + RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error()); + goto done; + } + mpi2mem(&sig->r, &sigbuf[32 - mpi_bytes(&sig->r)]); + mpi2mem(&sig->s, &sigbuf[64 - mpi_bytes(&sig->s)]); + + if (EVP_DigestVerify(md, sigbuf, 64, hash, hash_len) > 0) { + ret = RNP_SUCCESS; + } +done: + /* line below will also free ctx */ + EVP_MD_CTX_free(md); + EVP_PKEY_free(evpkey); + return ret; +} + +rnp_result_t +eddsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + if (!mpi_bytes(&key->x)) { + RNP_LOG("private key not set"); + return RNP_ERROR_BAD_PARAMETERS; + } + EVP_PKEY *evpkey = ec_load_key(key->p, &key->x, PGP_CURVE_ED25519); + if (!evpkey) { + RNP_LOG("Failed to load private key: %lu", ERR_peek_last_error()); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + /* init context and sign */ + EVP_PKEY_CTX *ctx = NULL; + EVP_MD_CTX * md = EVP_MD_CTX_new(); + if (!md) { + RNP_LOG("Failed to allocate MD ctx: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_DigestSignInit(md, &ctx, NULL, NULL, evpkey) <= 0) { + RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error()); + goto done; + } + static_assert((sizeof(sig->r.mpi) == PGP_MPINT_SIZE) && (PGP_MPINT_SIZE >= 64), + "invalid mpi type/size"); + sig->r.len = PGP_MPINT_SIZE; + if (EVP_DigestSign(md, sig->r.mpi, &sig->r.len, hash, hash_len) <= 0) { + RNP_LOG("Signing failed: %lu", ERR_peek_last_error()); + sig->r.len = 0; + goto done; + } + assert(sig->r.len == 64); + sig->r.len = 32; + sig->s.len = 32; + memcpy(sig->s.mpi, &sig->r.mpi[32], 32); + ret = RNP_SUCCESS; +done: + /* line below will also free ctx */ + EVP_MD_CTX_free(md); + EVP_PKEY_free(evpkey); + return ret; +} diff --git a/src/lib/crypto/elgamal.cpp b/src/lib/crypto/elgamal.cpp new file mode 100644 index 0000000..acebf4d --- /dev/null +++ b/src/lib/crypto/elgamal.cpp @@ -0,0 +1,302 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <string.h> +#include <botan/ffi.h> +#include <botan/bigint.h> +#include <botan/numthry.h> +#include <botan/reducer.h> +#include <rnp/rnp_def.h> +#include "elgamal.h" +#include "utils.h" +#include "bn.h" + +// Max supported key byte size +#define ELGAMAL_MAX_P_BYTELEN BITS_TO_BYTES(PGP_MPINT_BITS) + +static bool +elgamal_load_public_key(botan_pubkey_t *pubkey, const pgp_eg_key_t *keydata) +{ + bignum_t *p = NULL; + bignum_t *g = NULL; + bignum_t *y = NULL; + bool res = false; + + // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + if (mpi_bytes(&keydata->p) > ELGAMAL_MAX_P_BYTELEN) { + goto done; + } + + if (!(p = mpi2bn(&keydata->p)) || !(g = mpi2bn(&keydata->g)) || + !(y = mpi2bn(&keydata->y))) { + goto done; + } + + res = + !botan_pubkey_load_elgamal(pubkey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(g), BN_HANDLE_PTR(y)); +done: + bn_free(p); + bn_free(g); + bn_free(y); + return res; +} + +static bool +elgamal_load_secret_key(botan_privkey_t *seckey, const pgp_eg_key_t *keydata) +{ + bignum_t *p = NULL; + bignum_t *g = NULL; + bignum_t *x = NULL; + bool res = false; + + // Check if provided secret key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + if (mpi_bytes(&keydata->p) > ELGAMAL_MAX_P_BYTELEN) { + goto done; + } + + if (!(p = mpi2bn(&keydata->p)) || !(g = mpi2bn(&keydata->g)) || + !(x = mpi2bn(&keydata->x))) { + goto done; + } + + res = !botan_privkey_load_elgamal( + seckey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(g), BN_HANDLE_PTR(x)); +done: + bn_free(p); + bn_free(g); + bn_free(x); + return res; +} + +bool +elgamal_validate_key(const pgp_eg_key_t *key, bool secret) +{ + // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + if (mpi_bytes(&key->p) > ELGAMAL_MAX_P_BYTELEN) { + return false; + } + + /* Use custom validation since we added some custom validation, and Botan has slow test for + * prime for p */ + try { + Botan::BigInt p(key->p.mpi, key->p.len); + Botan::BigInt g(key->g.mpi, key->g.len); + + /* 1 < g < p */ + if ((g.cmp_word(1) != 1) || (g.cmp(p) != -1)) { + return false; + } + /* g ^ (p - 1) = 1 mod p */ + if (Botan::power_mod(g, p - 1, p).cmp_word(1)) { + return false; + } + /* check for small order subgroups */ + Botan::Modular_Reducer reducer(p); + Botan::BigInt v = g; + for (size_t i = 2; i < (1 << 17); i++) { + v = reducer.multiply(v, g); + if (!v.cmp_word(1)) { + RNP_LOG("Small subgroup detected. Order %zu", i); + return false; + } + } + if (!secret) { + return true; + } + /* check that g ^ x = y (mod p) */ + Botan::BigInt y(key->y.mpi, key->y.len); + Botan::BigInt x(key->x.mpi, key->x.len); + return Botan::power_mod(g, x, p) == y; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +rnp_result_t +elgamal_encrypt_pkcs1(rnp::RNG * rng, + pgp_eg_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_eg_key_t *key) +{ + botan_pubkey_t b_key = NULL; + botan_pk_op_encrypt_t op_ctx = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + /* Max size of an output len is twice an order of underlying group (p length) */ + uint8_t enc_buf[ELGAMAL_MAX_P_BYTELEN * 2] = {0}; + size_t p_len; + + if (!elgamal_load_public_key(&b_key, key)) { + RNP_LOG("Failed to load public key"); + goto end; + } + + /* Size of output buffer must be equal to twice the size of key byte len. + * as ElGamal encryption outputs concatenation of two components, both + * of size equal to size of public key byte len. + * Successful call to botan's ElGamal encryption will return output that's + * always 2*pubkey size. + */ + p_len = mpi_bytes(&key->p) * 2; + + if (botan_pk_op_encrypt_create(&op_ctx, b_key, "PKCS1v15", 0) || + botan_pk_op_encrypt(op_ctx, rng->handle(), enc_buf, &p_len, in, in_len)) { + RNP_LOG("Failed to create operation context"); + goto end; + } + + /* + * Botan's ElGamal formats the g^k and msg*(y^k) together into a single byte string. + * We have to parse out the two values after encryption, as rnp stores those values + * separatelly. + * + * We don't trim zeros from octet string as it is done before final marshalling + * (add_packet_body_mpi) + * + * We must assume that botan copies even number of bytes to output buffer (to avoid + * memory corruption) + */ + p_len /= 2; + if (mem2mpi(&out->g, enc_buf, p_len) && mem2mpi(&out->m, enc_buf + p_len, p_len)) { + ret = RNP_SUCCESS; + } +end: + botan_pk_op_encrypt_destroy(op_ctx); + botan_pubkey_destroy(b_key); + return ret; +} + +rnp_result_t +elgamal_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_eg_encrypted_t *in, + const pgp_eg_key_t * key) +{ + botan_privkey_t b_key = NULL; + botan_pk_op_decrypt_t op_ctx = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + uint8_t enc_buf[ELGAMAL_MAX_P_BYTELEN * 2] = {0}; + size_t p_len; + size_t g_len; + size_t m_len; + + if (!mpi_bytes(&key->x)) { + RNP_LOG("empty secret key"); + goto end; + } + + // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + p_len = mpi_bytes(&key->p); + g_len = mpi_bytes(&in->g); + m_len = mpi_bytes(&in->m); + + if ((2 * p_len > sizeof(enc_buf)) || (g_len > p_len) || (m_len > p_len)) { + RNP_LOG("Unsupported/wrong public key or encrypted data"); + goto end; + } + + if (!elgamal_load_secret_key(&b_key, key)) { + RNP_LOG("Failed to load private key"); + goto end; + } + + /* Botan expects ciphertext to be concatenated (g^k | encrypted m). Size must + * be equal to twice the byte size of public key, potentially prepended with zeros. + */ + memcpy(&enc_buf[p_len - g_len], in->g.mpi, g_len); + memcpy(&enc_buf[2 * p_len - m_len], in->m.mpi, m_len); + + *out_len = p_len; + if (botan_pk_op_decrypt_create(&op_ctx, b_key, "PKCS1v15", 0) || + botan_pk_op_decrypt(op_ctx, out, out_len, enc_buf, 2 * p_len)) { + RNP_LOG("Decryption failed"); + goto end; + } + ret = RNP_SUCCESS; +end: + botan_pk_op_decrypt_destroy(op_ctx); + botan_privkey_destroy(b_key); + return ret; +} + +rnp_result_t +elgamal_generate(rnp::RNG *rng, pgp_eg_key_t *key, size_t keybits) +{ + if ((keybits < 1024) || (keybits > PGP_MPINT_BITS)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + botan_privkey_t key_priv = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + bignum_t * p = bn_new(); + bignum_t * g = bn_new(); + bignum_t * y = bn_new(); + bignum_t * x = bn_new(); + + if (!p || !g || !y || !x) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + +start: + if (botan_privkey_create_elgamal(&key_priv, rng->handle(), keybits, keybits - 1)) { + RNP_LOG("Wrong parameters"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + + if (botan_privkey_get_field(BN_HANDLE_PTR(y), key_priv, "y")) { + RNP_LOG("Failed to obtain public key"); + goto end; + } + if (bn_num_bytes(*y) < BITS_TO_BYTES(keybits)) { + botan_privkey_destroy(key_priv); + goto start; + } + + if (botan_privkey_get_field(BN_HANDLE_PTR(p), key_priv, "p") || + botan_privkey_get_field(BN_HANDLE_PTR(g), key_priv, "g") || + botan_privkey_get_field(BN_HANDLE_PTR(y), key_priv, "y") || + botan_privkey_get_field(BN_HANDLE_PTR(x), key_priv, "x")) { + RNP_LOG("Botan FFI call failed"); + ret = RNP_ERROR_GENERIC; + goto end; + } + + if (bn2mpi(p, &key->p) && bn2mpi(g, &key->g) && bn2mpi(y, &key->y) && bn2mpi(x, &key->x)) { + ret = RNP_SUCCESS; + } +end: + bn_free(p); + bn_free(g); + bn_free(y); + bn_free(x); + botan_privkey_destroy(key_priv); + return ret; +} diff --git a/src/lib/crypto/elgamal.h b/src/lib/crypto/elgamal.h new file mode 100644 index 0000000..42d0555 --- /dev/null +++ b/src/lib/crypto/elgamal.h @@ -0,0 +1,116 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_ELG_H_ +#define RNP_ELG_H_ + +#include <stdint.h> +#include "crypto/rng.h" +#include "crypto/mpi.h" + +typedef struct pgp_eg_key_t { + pgp_mpi_t p; + pgp_mpi_t g; + pgp_mpi_t y; + /* secret mpi */ + pgp_mpi_t x; +} pgp_eg_key_t; + +typedef struct pgp_eg_signature_t { + /* This is kept only for packet reading. Implementation MUST + * not create elgamal signatures */ + pgp_mpi_t r; + pgp_mpi_t s; +} pgp_eg_signature_t; + +typedef struct pgp_eg_encrypted_t { + pgp_mpi_t g; + pgp_mpi_t m; +} pgp_eg_encrypted_t; + +bool elgamal_validate_key(const pgp_eg_key_t *key, bool secret); + +/* + * Performs ElGamal encryption + * Result of an encryption is composed of two parts - g2k and encm + * + * @param rng initialized rnp::RNG + * @param out encryption result + * @param in plaintext to be encrypted + * @param in_len length of the plaintext + * @param key public key to be used for encryption + * + * @pre out: must be valid pointer to corresponding structure + * @pre in_len: can't be bigger than byte size of `p' + * + * @return RNP_SUCCESS + * RNP_ERROR_OUT_OF_MEMORY allocation failure + * RNP_ERROR_BAD_PARAMETERS wrong input provided + */ +rnp_result_t elgamal_encrypt_pkcs1(rnp::RNG * rng, + pgp_eg_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_eg_key_t *key); + +/* + * Performs ElGamal decryption + * + * @param rng initialized rnp::RNG + * @param out decrypted plaintext. Must be capable of storing at least as much bytes as p size + * @param out_len number of plaintext bytes written will be put here + * @param in encrypted data + * @param key private key + * + * @pre out, in: must be valid pointers + * @pre out: length must be long enough to store decrypted data. Max size of + * decrypted data is equal to bytes size of `p' + * + * @return RNP_SUCCESS + * RNP_ERROR_OUT_OF_MEMORY allocation failure + * RNP_ERROR_BAD_PARAMETERS wrong input provided + */ +rnp_result_t elgamal_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_eg_encrypted_t *in, + const pgp_eg_key_t * key); + +/* + * Generates ElGamal key + * + * @param rng pointer to PRNG + * @param key generated key + * @param keybits key bitlen + * + * @pre `keybits' > 1024 + * + * @returns RNP_ERROR_BAD_PARAMETERS wrong parameters provided + * RNP_ERROR_GENERIC internal error + * RNP_SUCCESS key generated and copied to `seckey' + */ +rnp_result_t elgamal_generate(rnp::RNG *rng, pgp_eg_key_t *key, size_t keybits); +#endif diff --git a/src/lib/crypto/elgamal_ossl.cpp b/src/lib/crypto/elgamal_ossl.cpp new file mode 100644 index 0000000..f3fa381 --- /dev/null +++ b/src/lib/crypto/elgamal_ossl.cpp @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <cstdlib> +#include <string> +#include <cassert> +#include <rnp/rnp_def.h> +#include "elgamal.h" +#include "dl_ossl.h" +#include "utils.h" +#include "bn.h" +#include "mem.h" +#include <openssl/bn.h> +#include <openssl/dh.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/rand.h> + +// Max supported key byte size +#define ELGAMAL_MAX_P_BYTELEN BITS_TO_BYTES(PGP_MPINT_BITS) + +bool +elgamal_validate_key(const pgp_eg_key_t *key, bool secret) +{ + BN_CTX *ctx = BN_CTX_new(); + if (!ctx) { + RNP_LOG("Allocation failed."); + return false; + } + BN_CTX_start(ctx); + bool res = false; + bignum_t * p = mpi2bn(&key->p); + bignum_t * g = mpi2bn(&key->g); + bignum_t * p1 = BN_CTX_get(ctx); + bignum_t * r = BN_CTX_get(ctx); + bignum_t * y = NULL; + bignum_t * x = NULL; + BN_RECP_CTX *rctx = NULL; + + if (!p || !g || !p1 || !r) { + goto done; + } + + /* 1 < g < p */ + if ((BN_cmp(g, BN_value_one()) != 1) || (BN_cmp(g, p) != -1)) { + RNP_LOG("Invalid g value."); + goto done; + } + /* g ^ (p - 1) = 1 mod p */ + if (!BN_copy(p1, p) || !BN_sub_word(p1, 1) || !BN_mod_exp(r, g, p1, p, ctx)) { + RNP_LOG("g exp failed."); + goto done; + } + if (BN_cmp(r, BN_value_one()) != 0) { + RNP_LOG("Wrong g exp value."); + goto done; + } + /* check for small order subgroups */ + rctx = BN_RECP_CTX_new(); + if (!rctx || !BN_RECP_CTX_set(rctx, p, ctx) || !BN_copy(r, g)) { + RNP_LOG("Failed to init RECP context."); + goto done; + } + for (size_t i = 2; i < (1 << 17); i++) { + if (!BN_mod_mul_reciprocal(r, r, g, rctx, ctx)) { + RNP_LOG("Multiplication failed."); + goto done; + } + if (BN_cmp(r, BN_value_one()) == 0) { + RNP_LOG("Small subgroup detected. Order %zu", i); + goto done; + } + } + if (!secret) { + res = true; + goto done; + } + /* check that g ^ x = y (mod p) */ + x = mpi2bn(&key->x); + y = mpi2bn(&key->y); + if (!x || !y) { + goto done; + } + res = BN_mod_exp(r, g, x, p, ctx) && !BN_cmp(r, y); +done: + BN_CTX_free(ctx); + BN_RECP_CTX_free(rctx); + bn_free(p); + bn_free(g); + bn_free(y); + bn_free(x); + return res; +} + +static bool +pkcs1v15_pad(uint8_t *out, size_t out_len, const uint8_t *in, size_t in_len) +{ + assert(out && in); + if (out_len < in_len + 11) { + return false; + } + out[0] = 0x00; + out[1] = 0x02; + size_t rnd = out_len - in_len - 3; + out[2 + rnd] = 0x00; + if (RAND_bytes(&out[2], rnd) != 1) { + return false; + } + for (size_t i = 2; i < 2 + rnd; i++) { + /* we need non-zero bytes */ + size_t cntr = 16; + while (!out[i] && (cntr--) && (RAND_bytes(&out[i], 1) == 1)) { + } + if (!out[i]) { + RNP_LOG("Something is wrong with RNG."); + return false; + } + } + memcpy(out + rnd + 3, in, in_len); + return true; +} + +static bool +pkcs1v15_unpad(size_t *padlen, const uint8_t *in, size_t in_len, bool skip0) +{ + if (in_len <= (size_t)(11 - skip0)) { + return false; + } + if (!skip0 && in[0]) { + return false; + } + if (in[1 - skip0] != 0x02) { + return false; + } + size_t pad = 2 - skip0; + while ((pad < in_len) && in[pad]) { + pad++; + } + if (pad >= in_len) { + return false; + } + *padlen = pad + 1; + return true; +} + +rnp_result_t +elgamal_encrypt_pkcs1(rnp::RNG * rng, + pgp_eg_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_eg_key_t *key) +{ + pgp_mpi_t mm = {}; + mm.len = key->p.len; + if (!pkcs1v15_pad(mm.mpi, mm.len, in, in_len)) { + RNP_LOG("Failed to add PKCS1 v1.5 padding."); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_result_t ret = RNP_ERROR_GENERIC; + BN_CTX * ctx = BN_CTX_new(); + if (!ctx) { + RNP_LOG("Allocation failed."); + return RNP_ERROR_OUT_OF_MEMORY; + } + BN_CTX_start(ctx); + BN_MONT_CTX *mctx = BN_MONT_CTX_new(); + bignum_t * m = mpi2bn(&mm); + bignum_t * p = mpi2bn(&key->p); + bignum_t * g = mpi2bn(&key->g); + bignum_t * y = mpi2bn(&key->y); + bignum_t * c1 = BN_CTX_get(ctx); + bignum_t * c2 = BN_CTX_get(ctx); + bignum_t * k = BN_secure_new(); + if (!mctx || !m || !p || !g || !y || !c1 || !c2 || !k) { + RNP_LOG("Allocation failed."); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + /* initialize Montgomery context */ + if (BN_MONT_CTX_set(mctx, p, ctx) < 1) { + RNP_LOG("Failed to setup Montgomery context."); + goto done; + } + int res; + /* must not fail */ + res = BN_rshift1(c1, p); + assert(res == 1); + if (res < 1) { + RNP_LOG("BN_rshift1 failed."); + goto done; + } + /* generate k */ + if (BN_rand_range(k, c1) < 1) { + RNP_LOG("Failed to generate k."); + goto done; + } + /* calculate c1 = g ^ k (mod p) */ + if (BN_mod_exp_mont_consttime(c1, g, k, p, ctx, mctx) < 1) { + RNP_LOG("Exponentiation 1 failed"); + goto done; + } + /* calculate c2 = m * y ^ k (mod p)*/ + if (BN_mod_exp_mont_consttime(c2, y, k, p, ctx, mctx) < 1) { + RNP_LOG("Exponentiation 2 failed"); + goto done; + } + if (BN_mod_mul(c2, c2, m, p, ctx) < 1) { + RNP_LOG("Multiplication failed"); + goto done; + } + res = bn2mpi(c1, &out->g) && bn2mpi(c2, &out->m); + assert(res == 1); + ret = RNP_SUCCESS; +done: + BN_MONT_CTX_free(mctx); + BN_CTX_free(ctx); + bn_free(m); + bn_free(p); + bn_free(g); + bn_free(y); + bn_free(k); + return ret; +} + +rnp_result_t +elgamal_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_eg_encrypted_t *in, + const pgp_eg_key_t * key) +{ + if (!mpi_bytes(&key->x)) { + RNP_LOG("Secret key not set."); + return RNP_ERROR_BAD_PARAMETERS; + } + BN_CTX *ctx = BN_CTX_new(); + if (!ctx) { + RNP_LOG("Allocation failed."); + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_mpi_t mm = {}; + size_t padlen = 0; + rnp_result_t ret = RNP_ERROR_GENERIC; + BN_CTX_start(ctx); + BN_MONT_CTX *mctx = BN_MONT_CTX_new(); + bignum_t * p = mpi2bn(&key->p); + bignum_t * g = mpi2bn(&key->g); + bignum_t * x = mpi2bn(&key->x); + bignum_t * c1 = mpi2bn(&in->g); + bignum_t * c2 = mpi2bn(&in->m); + bignum_t * s = BN_CTX_get(ctx); + bignum_t * m = BN_secure_new(); + if (!mctx || !p || !g || !x || !c1 || !c2 || !m) { + RNP_LOG("Allocation failed."); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + /* initialize Montgomery context */ + if (BN_MONT_CTX_set(mctx, p, ctx) < 1) { + RNP_LOG("Failed to setup Montgomery context."); + goto done; + } + /* calculate s = c1 ^ x (mod p) */ + if (BN_mod_exp_mont_consttime(s, c1, x, p, ctx, mctx) < 1) { + RNP_LOG("Exponentiation 1 failed"); + goto done; + } + /* calculate s^-1 (mod p) */ + BN_set_flags(s, BN_FLG_CONSTTIME); + if (!BN_mod_inverse(s, s, p, ctx)) { + RNP_LOG("Failed to calculate inverse."); + goto done; + } + /* calculate m = c2 * s ^ -1 (mod p)*/ + if (BN_mod_mul(m, c2, s, p, ctx) < 1) { + RNP_LOG("Multiplication failed"); + goto done; + } + bool res; + res = bn2mpi(m, &mm); + assert(res); + if (!res) { + RNP_LOG("bn2mpi failed."); + goto done; + } + /* unpad, handling skipped leftmost 0 case */ + if (!pkcs1v15_unpad(&padlen, mm.mpi, mm.len, mm.len == key->p.len - 1)) { + RNP_LOG("Unpad failed."); + goto done; + } + *out_len = mm.len - padlen; + memcpy(out, &mm.mpi[padlen], *out_len); + ret = RNP_SUCCESS; +done: + secure_clear(mm.mpi, PGP_MPINT_SIZE); + BN_MONT_CTX_free(mctx); + BN_CTX_free(ctx); + bn_free(p); + bn_free(g); + bn_free(x); + bn_free(c1); + bn_free(c2); + bn_free(m); + return ret; +} + +rnp_result_t +elgamal_generate(rnp::RNG *rng, pgp_eg_key_t *key, size_t keybits) +{ + if ((keybits < 1024) || (keybits > PGP_MPINT_BITS)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + const DH * dh = NULL; + EVP_PKEY * pkey = NULL; + EVP_PKEY * parmkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + + /* Generate DH params, which usable for ElGamal as well */ + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + return ret; + } + if (EVP_PKEY_paramgen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx, keybits) <= 0) { + RNP_LOG("Failed to set key bits: %lu", ERR_peek_last_error()); + goto done; + } + /* OpenSSL correctly handles case with g = 5, making sure that g is primitive root of + * q-group */ + if (EVP_PKEY_CTX_set_dh_paramgen_generator(ctx, DH_GENERATOR_5) <= 0) { + RNP_LOG("Failed to set key generator: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_paramgen(ctx, &parmkey) <= 0) { + RNP_LOG("Failed to generate parameters: %lu", ERR_peek_last_error()); + goto done; + } + EVP_PKEY_CTX_free(ctx); + /* Generate DH (ElGamal) key */ +start: + ctx = EVP_PKEY_CTX_new(parmkey, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + RNP_LOG("ElGamal keygen failed: %lu", ERR_peek_last_error()); + goto done; + } + dh = EVP_PKEY_get0_DH(pkey); + if (!dh) { + RNP_LOG("Failed to retrieve DH key: %lu", ERR_peek_last_error()); + goto done; + } + if (BITS_TO_BYTES(BN_num_bits(DH_get0_pub_key(dh))) != BITS_TO_BYTES(keybits)) { + EVP_PKEY_CTX_free(ctx); + ctx = NULL; + EVP_PKEY_free(pkey); + pkey = NULL; + goto start; + } + + const bignum_t *p; + const bignum_t *g; + const bignum_t *y; + const bignum_t *x; + p = DH_get0_p(dh); + g = DH_get0_g(dh); + y = DH_get0_pub_key(dh); + x = DH_get0_priv_key(dh); + if (!p || !g || !y || !x) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + bn2mpi(p, &key->p); + bn2mpi(g, &key->g); + bn2mpi(y, &key->y); + bn2mpi(x, &key->x); + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(parmkey); + EVP_PKEY_free(pkey); + return ret; +} diff --git a/src/lib/crypto/hash.cpp b/src/lib/crypto/hash.cpp new file mode 100644 index 0000000..250deec --- /dev/null +++ b/src/lib/crypto/hash.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "hash_botan.hpp" +#include "logging.h" +#include <cassert> + +static const id_str_pair botan_alg_map[] = { + {PGP_HASH_MD5, "MD5"}, + {PGP_HASH_SHA1, "SHA-1"}, + {PGP_HASH_RIPEMD, "RIPEMD-160"}, + {PGP_HASH_SHA256, "SHA-256"}, + {PGP_HASH_SHA384, "SHA-384"}, + {PGP_HASH_SHA512, "SHA-512"}, + {PGP_HASH_SHA224, "SHA-224"}, +#if defined(ENABLE_SM2) + {PGP_HASH_SM3, "SM3"}, +#endif + {PGP_HASH_SHA3_256, "SHA-3(256)"}, + {PGP_HASH_SHA3_512, "SHA-3(512)"}, + {0, NULL}, +}; + +namespace rnp { + +Hash_Botan::Hash_Botan(pgp_hash_alg_t alg) : Hash(alg) +{ + auto name = Hash_Botan::name_backend(alg); + if (!name) { + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + fn_ = Botan::HashFunction::create(name); + if (!fn_) { + RNP_LOG("Error creating hash object for '%s'", name); + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + assert(size_ == fn_->output_length()); +} + +Hash_Botan::Hash_Botan(const Hash_Botan &src) : Hash(src.alg_) +{ + if (!src.fn_) { + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + fn_ = src.fn_->copy_state(); +} + +Hash_Botan::~Hash_Botan() +{ +} + +std::unique_ptr<Hash_Botan> +Hash_Botan::create(pgp_hash_alg_t alg) +{ + return std::unique_ptr<Hash_Botan>(new Hash_Botan(alg)); +} + +std::unique_ptr<Hash> +Hash_Botan::clone() const +{ + return std::unique_ptr<Hash>(new Hash_Botan(*this)); +} + +void +Hash_Botan::add(const void *buf, size_t len) +{ + if (!fn_) { + throw rnp_exception(RNP_ERROR_NULL_POINTER); + } + fn_->update(static_cast<const uint8_t *>(buf), len); +} + +size_t +Hash_Botan::finish(uint8_t *digest) +{ + if (!fn_) { + return 0; + } + size_t outlen = size_; + if (digest) { + fn_->final(digest); + } + fn_ = nullptr; + size_ = 0; + return outlen; +} + +const char * +Hash_Botan::name_backend(pgp_hash_alg_t alg) +{ + return id_str_pair::lookup(botan_alg_map, alg); +} + +CRC24_Botan::CRC24_Botan() +{ + fn_ = Botan::HashFunction::create("CRC24"); + if (!fn_) { + RNP_LOG("Error creating CRC24 object"); + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + assert(3 == fn_->output_length()); +} + +CRC24_Botan::~CRC24_Botan() +{ +} + +std::unique_ptr<CRC24_Botan> +CRC24_Botan::create() +{ + return std::unique_ptr<CRC24_Botan>(new CRC24_Botan()); +} + +void +CRC24_Botan::add(const void *buf, size_t len) +{ + if (!fn_) { + throw rnp_exception(RNP_ERROR_NULL_POINTER); + } + fn_->update(static_cast<const uint8_t *>(buf), len); +} + +std::array<uint8_t, 3> +CRC24_Botan::finish() +{ + if (!fn_) { + throw rnp_exception(RNP_ERROR_NULL_POINTER); + } + std::array<uint8_t, 3> crc{}; + fn_->final(crc.data()); + fn_ = nullptr; + return crc; +} + +} // namespace rnp diff --git a/src/lib/crypto/hash.hpp b/src/lib/crypto/hash.hpp new file mode 100644 index 0000000..7fcb817 --- /dev/null +++ b/src/lib/crypto/hash.hpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_HASH_H_ +#define CRYPTO_HASH_H_ + +#include <repgp/repgp_def.h> +#include "types.h" +#include "config.h" +#include <memory> +#include <vector> +#include <array> + +/** + * Output size (in bytes) of biggest supported hash algo + */ +#define PGP_MAX_HASH_SIZE (64) + +namespace rnp { +class Hash { + protected: + pgp_hash_alg_t alg_; + size_t size_; + Hash(pgp_hash_alg_t alg) : alg_(alg) + { + size_ = Hash::size(alg); + }; + + public: + pgp_hash_alg_t alg() const; + size_t size() const; + + static std::unique_ptr<Hash> create(pgp_hash_alg_t alg); + virtual std::unique_ptr<Hash> clone() const = 0; + + virtual void add(const void *buf, size_t len) = 0; + virtual void add(uint32_t val); + virtual void add(const pgp_mpi_t &mpi); + virtual size_t finish(uint8_t *digest = NULL) = 0; + + virtual ~Hash(); + + /* Hash algorithm by string representation from cleartext-signed text */ + static pgp_hash_alg_t alg(const char *name); + /* Hash algorithm representation for cleartext-signed text */ + static const char *name(pgp_hash_alg_t alg); + /* Size of the hash algorithm output or 0 if algorithm is unknown */ + static size_t size(pgp_hash_alg_t alg); +}; + +class CRC24 { + protected: + CRC24(){}; + + public: + static std::unique_ptr<CRC24> create(); + + virtual void add(const void *buf, size_t len) = 0; + virtual std::array<uint8_t, 3> finish() = 0; + + virtual ~CRC24(){}; +}; + +class HashList { + public: + std::vector<std::unique_ptr<Hash>> hashes; + + void add_alg(pgp_hash_alg_t alg); + const Hash *get(pgp_hash_alg_t alg) const; + void add(const void *buf, size_t len); +}; + +} // namespace rnp + +#endif diff --git a/src/lib/crypto/hash_botan.hpp b/src/lib/crypto/hash_botan.hpp new file mode 100644 index 0000000..942e3a8 --- /dev/null +++ b/src/lib/crypto/hash_botan.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_HASH_BOTAN_HPP_ +#define CRYPTO_HASH_BOTAN_HPP_ + +#include "hash.hpp" +#include <botan/hash.h> + +namespace rnp { +class Hash_Botan : public Hash { + private: + std::unique_ptr<Botan::HashFunction> fn_; + + Hash_Botan(pgp_hash_alg_t alg); + Hash_Botan(const Hash_Botan &src); + + public: + virtual ~Hash_Botan(); + + static std::unique_ptr<Hash_Botan> create(pgp_hash_alg_t alg); + std::unique_ptr<Hash> clone() const override; + + void add(const void *buf, size_t len) override; + size_t finish(uint8_t *digest = NULL) override; + + static const char *name_backend(pgp_hash_alg_t alg); +}; + +class CRC24_Botan : public CRC24 { + std::unique_ptr<Botan::HashFunction> fn_; + CRC24_Botan(); + + public: + virtual ~CRC24_Botan(); + + static std::unique_ptr<CRC24_Botan> create(); + + void add(const void *buf, size_t len) override; + std::array<uint8_t, 3> finish() override; +}; + +} // namespace rnp + +#endif
\ No newline at end of file diff --git a/src/lib/crypto/hash_common.cpp b/src/lib/crypto/hash_common.cpp new file mode 100644 index 0000000..69b400b --- /dev/null +++ b/src/lib/crypto/hash_common.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2021-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "hash.hpp" +#include "types.h" +#include "utils.h" +#include "str-utils.h" +#include "hash_sha1cd.hpp" +#if defined(CRYPTO_BACKEND_BOTAN) +#include "hash_botan.hpp" +#endif +#if defined(CRYPTO_BACKEND_OPENSSL) +#include "hash_ossl.hpp" +#include "hash_crc24.hpp" +#endif + +static const struct hash_alg_map_t { + pgp_hash_alg_t type; + const char * name; + size_t len; +} hash_alg_map[] = {{PGP_HASH_MD5, "MD5", 16}, + {PGP_HASH_SHA1, "SHA1", 20}, + {PGP_HASH_RIPEMD, "RIPEMD160", 20}, + {PGP_HASH_SHA256, "SHA256", 32}, + {PGP_HASH_SHA384, "SHA384", 48}, + {PGP_HASH_SHA512, "SHA512", 64}, + {PGP_HASH_SHA224, "SHA224", 28}, + {PGP_HASH_SM3, "SM3", 32}, + {PGP_HASH_SHA3_256, "SHA3-256", 32}, + {PGP_HASH_SHA3_512, "SHA3-512", 64}}; + +namespace rnp { + +pgp_hash_alg_t +Hash::alg() const +{ + return alg_; +} + +size_t +Hash::size() const +{ + return Hash::size(alg_); +} + +std::unique_ptr<Hash> +Hash::create(pgp_hash_alg_t alg) +{ + if (alg == PGP_HASH_SHA1) { + return Hash_SHA1CD::create(); + } +#if !defined(ENABLE_SM2) + if (alg == PGP_HASH_SM3) { + RNP_LOG("SM3 hash is not available."); + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +#endif +#if defined(CRYPTO_BACKEND_OPENSSL) + return Hash_OpenSSL::create(alg); +#elif defined(CRYPTO_BACKEND_BOTAN) + return Hash_Botan::create(alg); +#else +#error "Crypto backend not specified" +#endif +} + +std::unique_ptr<CRC24> +CRC24::create() +{ +#if defined(CRYPTO_BACKEND_OPENSSL) + return CRC24_RNP::create(); +#elif defined(CRYPTO_BACKEND_BOTAN) + return CRC24_Botan::create(); +#else +#error "Crypto backend not specified" +#endif +} + +void +Hash::add(uint32_t val) +{ + uint8_t ibuf[4]; + STORE32BE(ibuf, val); + add(ibuf, sizeof(ibuf)); +} + +void +Hash::add(const pgp_mpi_t &val) +{ + size_t len = mpi_bytes(&val); + size_t idx = 0; + while ((idx < len) && (!val.mpi[idx])) { + idx++; + } + + if (idx >= len) { + add(0); + return; + } + + add(len - idx); + if (val.mpi[idx] & 0x80) { + uint8_t padbyte = 0; + add(&padbyte, 1); + } + add(val.mpi + idx, len - idx); +} + +Hash::~Hash() +{ +} + +pgp_hash_alg_t +Hash::alg(const char *name) +{ + if (!name) { + return PGP_HASH_UNKNOWN; + } + for (size_t i = 0; i < ARRAY_SIZE(hash_alg_map); i++) { + if (rnp::str_case_eq(name, hash_alg_map[i].name)) { + return hash_alg_map[i].type; + } + } + return PGP_HASH_UNKNOWN; +} + +const char * +Hash::name(pgp_hash_alg_t alg) +{ + const char *ret = NULL; + ARRAY_LOOKUP_BY_ID(hash_alg_map, type, name, alg, ret); + return ret; +} + +size_t +Hash::size(pgp_hash_alg_t alg) +{ + size_t val = 0; + ARRAY_LOOKUP_BY_ID(hash_alg_map, type, len, alg, val); + return val; +} + +void +HashList::add_alg(pgp_hash_alg_t alg) +{ + if (!get(alg)) { + hashes.emplace_back(rnp::Hash::create(alg)); + } +} + +const Hash * +HashList::get(pgp_hash_alg_t alg) const +{ + for (auto &hash : hashes) { + if (hash->alg() == alg) { + return hash.get(); + } + } + return NULL; +} + +void +HashList::add(const void *buf, size_t len) +{ + for (auto &hash : hashes) { + hash->add(buf, len); + } +} + +} // namespace rnp diff --git a/src/lib/crypto/hash_crc24.cpp b/src/lib/crypto/hash_crc24.cpp new file mode 100644 index 0000000..54f4d96 --- /dev/null +++ b/src/lib/crypto/hash_crc24.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdint.h> +#include <stddef.h> +#include "utils.h" +#include "hash_crc24.hpp" + +static const uint32_t T0[256] = { + 0x00000000, 0x00FB4C86, 0x000DD58A, 0x00F6990C, 0x00E1E693, 0x001AAA15, 0x00EC3319, + 0x00177F9F, 0x003981A1, 0x00C2CD27, 0x0034542B, 0x00CF18AD, 0x00D86732, 0x00232BB4, + 0x00D5B2B8, 0x002EFE3E, 0x00894EC5, 0x00720243, 0x00849B4F, 0x007FD7C9, 0x0068A856, + 0x0093E4D0, 0x00657DDC, 0x009E315A, 0x00B0CF64, 0x004B83E2, 0x00BD1AEE, 0x00465668, + 0x005129F7, 0x00AA6571, 0x005CFC7D, 0x00A7B0FB, 0x00E9D10C, 0x00129D8A, 0x00E40486, + 0x001F4800, 0x0008379F, 0x00F37B19, 0x0005E215, 0x00FEAE93, 0x00D050AD, 0x002B1C2B, + 0x00DD8527, 0x0026C9A1, 0x0031B63E, 0x00CAFAB8, 0x003C63B4, 0x00C72F32, 0x00609FC9, + 0x009BD34F, 0x006D4A43, 0x009606C5, 0x0081795A, 0x007A35DC, 0x008CACD0, 0x0077E056, + 0x00591E68, 0x00A252EE, 0x0054CBE2, 0x00AF8764, 0x00B8F8FB, 0x0043B47D, 0x00B52D71, + 0x004E61F7, 0x00D2A319, 0x0029EF9F, 0x00DF7693, 0x00243A15, 0x0033458A, 0x00C8090C, + 0x003E9000, 0x00C5DC86, 0x00EB22B8, 0x00106E3E, 0x00E6F732, 0x001DBBB4, 0x000AC42B, + 0x00F188AD, 0x000711A1, 0x00FC5D27, 0x005BEDDC, 0x00A0A15A, 0x00563856, 0x00AD74D0, + 0x00BA0B4F, 0x004147C9, 0x00B7DEC5, 0x004C9243, 0x00626C7D, 0x009920FB, 0x006FB9F7, + 0x0094F571, 0x00838AEE, 0x0078C668, 0x008E5F64, 0x007513E2, 0x003B7215, 0x00C03E93, + 0x0036A79F, 0x00CDEB19, 0x00DA9486, 0x0021D800, 0x00D7410C, 0x002C0D8A, 0x0002F3B4, + 0x00F9BF32, 0x000F263E, 0x00F46AB8, 0x00E31527, 0x001859A1, 0x00EEC0AD, 0x00158C2B, + 0x00B23CD0, 0x00497056, 0x00BFE95A, 0x0044A5DC, 0x0053DA43, 0x00A896C5, 0x005E0FC9, + 0x00A5434F, 0x008BBD71, 0x0070F1F7, 0x008668FB, 0x007D247D, 0x006A5BE2, 0x00911764, + 0x00678E68, 0x009CC2EE, 0x00A44733, 0x005F0BB5, 0x00A992B9, 0x0052DE3F, 0x0045A1A0, + 0x00BEED26, 0x0048742A, 0x00B338AC, 0x009DC692, 0x00668A14, 0x00901318, 0x006B5F9E, + 0x007C2001, 0x00876C87, 0x0071F58B, 0x008AB90D, 0x002D09F6, 0x00D64570, 0x0020DC7C, + 0x00DB90FA, 0x00CCEF65, 0x0037A3E3, 0x00C13AEF, 0x003A7669, 0x00148857, 0x00EFC4D1, + 0x00195DDD, 0x00E2115B, 0x00F56EC4, 0x000E2242, 0x00F8BB4E, 0x0003F7C8, 0x004D963F, + 0x00B6DAB9, 0x004043B5, 0x00BB0F33, 0x00AC70AC, 0x00573C2A, 0x00A1A526, 0x005AE9A0, + 0x0074179E, 0x008F5B18, 0x0079C214, 0x00828E92, 0x0095F10D, 0x006EBD8B, 0x00982487, + 0x00636801, 0x00C4D8FA, 0x003F947C, 0x00C90D70, 0x003241F6, 0x00253E69, 0x00DE72EF, + 0x0028EBE3, 0x00D3A765, 0x00FD595B, 0x000615DD, 0x00F08CD1, 0x000BC057, 0x001CBFC8, + 0x00E7F34E, 0x00116A42, 0x00EA26C4, 0x0076E42A, 0x008DA8AC, 0x007B31A0, 0x00807D26, + 0x009702B9, 0x006C4E3F, 0x009AD733, 0x00619BB5, 0x004F658B, 0x00B4290D, 0x0042B001, + 0x00B9FC87, 0x00AE8318, 0x0055CF9E, 0x00A35692, 0x00581A14, 0x00FFAAEF, 0x0004E669, + 0x00F27F65, 0x000933E3, 0x001E4C7C, 0x00E500FA, 0x001399F6, 0x00E8D570, 0x00C62B4E, + 0x003D67C8, 0x00CBFEC4, 0x0030B242, 0x0027CDDD, 0x00DC815B, 0x002A1857, 0x00D154D1, + 0x009F3526, 0x006479A0, 0x0092E0AC, 0x0069AC2A, 0x007ED3B5, 0x00859F33, 0x0073063F, + 0x00884AB9, 0x00A6B487, 0x005DF801, 0x00AB610D, 0x00502D8B, 0x00475214, 0x00BC1E92, + 0x004A879E, 0x00B1CB18, 0x00167BE3, 0x00ED3765, 0x001BAE69, 0x00E0E2EF, 0x00F79D70, + 0x000CD1F6, 0x00FA48FA, 0x0001047C, 0x002FFA42, 0x00D4B6C4, 0x00222FC8, 0x00D9634E, + 0x00CE1CD1, 0x00355057, 0x00C3C95B, 0x003885DD, +}; + +static const uint32_t T1[256] = { + 0x00000000, 0x00488F66, 0x00901ECD, 0x00D891AB, 0x00DB711C, 0x0093FE7A, 0x004B6FD1, + 0x0003E0B7, 0x00B6E338, 0x00FE6C5E, 0x0026FDF5, 0x006E7293, 0x006D9224, 0x00251D42, + 0x00FD8CE9, 0x00B5038F, 0x006CC771, 0x00244817, 0x00FCD9BC, 0x00B456DA, 0x00B7B66D, + 0x00FF390B, 0x0027A8A0, 0x006F27C6, 0x00DA2449, 0x0092AB2F, 0x004A3A84, 0x0002B5E2, + 0x00015555, 0x0049DA33, 0x00914B98, 0x00D9C4FE, 0x00D88EE3, 0x00900185, 0x0048902E, + 0x00001F48, 0x0003FFFF, 0x004B7099, 0x0093E132, 0x00DB6E54, 0x006E6DDB, 0x0026E2BD, + 0x00FE7316, 0x00B6FC70, 0x00B51CC7, 0x00FD93A1, 0x0025020A, 0x006D8D6C, 0x00B44992, + 0x00FCC6F4, 0x0024575F, 0x006CD839, 0x006F388E, 0x0027B7E8, 0x00FF2643, 0x00B7A925, + 0x0002AAAA, 0x004A25CC, 0x0092B467, 0x00DA3B01, 0x00D9DBB6, 0x009154D0, 0x0049C57B, + 0x00014A1D, 0x004B5141, 0x0003DE27, 0x00DB4F8C, 0x0093C0EA, 0x0090205D, 0x00D8AF3B, + 0x00003E90, 0x0048B1F6, 0x00FDB279, 0x00B53D1F, 0x006DACB4, 0x002523D2, 0x0026C365, + 0x006E4C03, 0x00B6DDA8, 0x00FE52CE, 0x00279630, 0x006F1956, 0x00B788FD, 0x00FF079B, + 0x00FCE72C, 0x00B4684A, 0x006CF9E1, 0x00247687, 0x00917508, 0x00D9FA6E, 0x00016BC5, + 0x0049E4A3, 0x004A0414, 0x00028B72, 0x00DA1AD9, 0x009295BF, 0x0093DFA2, 0x00DB50C4, + 0x0003C16F, 0x004B4E09, 0x0048AEBE, 0x000021D8, 0x00D8B073, 0x00903F15, 0x00253C9A, + 0x006DB3FC, 0x00B52257, 0x00FDAD31, 0x00FE4D86, 0x00B6C2E0, 0x006E534B, 0x0026DC2D, + 0x00FF18D3, 0x00B797B5, 0x006F061E, 0x00278978, 0x002469CF, 0x006CE6A9, 0x00B47702, + 0x00FCF864, 0x0049FBEB, 0x0001748D, 0x00D9E526, 0x00916A40, 0x00928AF7, 0x00DA0591, + 0x0002943A, 0x004A1B5C, 0x0096A282, 0x00DE2DE4, 0x0006BC4F, 0x004E3329, 0x004DD39E, + 0x00055CF8, 0x00DDCD53, 0x00954235, 0x002041BA, 0x0068CEDC, 0x00B05F77, 0x00F8D011, + 0x00FB30A6, 0x00B3BFC0, 0x006B2E6B, 0x0023A10D, 0x00FA65F3, 0x00B2EA95, 0x006A7B3E, + 0x0022F458, 0x002114EF, 0x00699B89, 0x00B10A22, 0x00F98544, 0x004C86CB, 0x000409AD, + 0x00DC9806, 0x00941760, 0x0097F7D7, 0x00DF78B1, 0x0007E91A, 0x004F667C, 0x004E2C61, + 0x0006A307, 0x00DE32AC, 0x0096BDCA, 0x00955D7D, 0x00DDD21B, 0x000543B0, 0x004DCCD6, + 0x00F8CF59, 0x00B0403F, 0x0068D194, 0x00205EF2, 0x0023BE45, 0x006B3123, 0x00B3A088, + 0x00FB2FEE, 0x0022EB10, 0x006A6476, 0x00B2F5DD, 0x00FA7ABB, 0x00F99A0C, 0x00B1156A, + 0x006984C1, 0x00210BA7, 0x00940828, 0x00DC874E, 0x000416E5, 0x004C9983, 0x004F7934, + 0x0007F652, 0x00DF67F9, 0x0097E89F, 0x00DDF3C3, 0x00957CA5, 0x004DED0E, 0x00056268, + 0x000682DF, 0x004E0DB9, 0x00969C12, 0x00DE1374, 0x006B10FB, 0x00239F9D, 0x00FB0E36, + 0x00B38150, 0x00B061E7, 0x00F8EE81, 0x00207F2A, 0x0068F04C, 0x00B134B2, 0x00F9BBD4, + 0x00212A7F, 0x0069A519, 0x006A45AE, 0x0022CAC8, 0x00FA5B63, 0x00B2D405, 0x0007D78A, + 0x004F58EC, 0x0097C947, 0x00DF4621, 0x00DCA696, 0x009429F0, 0x004CB85B, 0x0004373D, + 0x00057D20, 0x004DF246, 0x009563ED, 0x00DDEC8B, 0x00DE0C3C, 0x0096835A, 0x004E12F1, + 0x00069D97, 0x00B39E18, 0x00FB117E, 0x002380D5, 0x006B0FB3, 0x0068EF04, 0x00206062, + 0x00F8F1C9, 0x00B07EAF, 0x0069BA51, 0x00213537, 0x00F9A49C, 0x00B12BFA, 0x00B2CB4D, + 0x00FA442B, 0x0022D580, 0x006A5AE6, 0x00DF5969, 0x0097D60F, 0x004F47A4, 0x0007C8C2, + 0x00042875, 0x004CA713, 0x009436B8, 0x00DCB9DE, +}; + +static const uint32_t T2[256] = { + 0x00000000, 0x00D70983, 0x00555F80, 0x00825603, 0x0051F286, 0x0086FB05, 0x0004AD06, + 0x00D3A485, 0x0059A88B, 0x008EA108, 0x000CF70B, 0x00DBFE88, 0x00085A0D, 0x00DF538E, + 0x005D058D, 0x008A0C0E, 0x00491C91, 0x009E1512, 0x001C4311, 0x00CB4A92, 0x0018EE17, + 0x00CFE794, 0x004DB197, 0x009AB814, 0x0010B41A, 0x00C7BD99, 0x0045EB9A, 0x0092E219, + 0x0041469C, 0x00964F1F, 0x0014191C, 0x00C3109F, 0x006974A4, 0x00BE7D27, 0x003C2B24, + 0x00EB22A7, 0x00388622, 0x00EF8FA1, 0x006DD9A2, 0x00BAD021, 0x0030DC2F, 0x00E7D5AC, + 0x006583AF, 0x00B28A2C, 0x00612EA9, 0x00B6272A, 0x00347129, 0x00E378AA, 0x00206835, + 0x00F761B6, 0x007537B5, 0x00A23E36, 0x00719AB3, 0x00A69330, 0x0024C533, 0x00F3CCB0, + 0x0079C0BE, 0x00AEC93D, 0x002C9F3E, 0x00FB96BD, 0x00283238, 0x00FF3BBB, 0x007D6DB8, + 0x00AA643B, 0x0029A4CE, 0x00FEAD4D, 0x007CFB4E, 0x00ABF2CD, 0x00785648, 0x00AF5FCB, + 0x002D09C8, 0x00FA004B, 0x00700C45, 0x00A705C6, 0x002553C5, 0x00F25A46, 0x0021FEC3, + 0x00F6F740, 0x0074A143, 0x00A3A8C0, 0x0060B85F, 0x00B7B1DC, 0x0035E7DF, 0x00E2EE5C, + 0x00314AD9, 0x00E6435A, 0x00641559, 0x00B31CDA, 0x003910D4, 0x00EE1957, 0x006C4F54, + 0x00BB46D7, 0x0068E252, 0x00BFEBD1, 0x003DBDD2, 0x00EAB451, 0x0040D06A, 0x0097D9E9, + 0x00158FEA, 0x00C28669, 0x001122EC, 0x00C62B6F, 0x00447D6C, 0x009374EF, 0x001978E1, + 0x00CE7162, 0x004C2761, 0x009B2EE2, 0x00488A67, 0x009F83E4, 0x001DD5E7, 0x00CADC64, + 0x0009CCFB, 0x00DEC578, 0x005C937B, 0x008B9AF8, 0x00583E7D, 0x008F37FE, 0x000D61FD, + 0x00DA687E, 0x00506470, 0x00876DF3, 0x00053BF0, 0x00D23273, 0x000196F6, 0x00D69F75, + 0x0054C976, 0x0083C0F5, 0x00A9041B, 0x007E0D98, 0x00FC5B9B, 0x002B5218, 0x00F8F69D, + 0x002FFF1E, 0x00ADA91D, 0x007AA09E, 0x00F0AC90, 0x0027A513, 0x00A5F310, 0x0072FA93, + 0x00A15E16, 0x00765795, 0x00F40196, 0x00230815, 0x00E0188A, 0x00371109, 0x00B5470A, + 0x00624E89, 0x00B1EA0C, 0x0066E38F, 0x00E4B58C, 0x0033BC0F, 0x00B9B001, 0x006EB982, + 0x00ECEF81, 0x003BE602, 0x00E84287, 0x003F4B04, 0x00BD1D07, 0x006A1484, 0x00C070BF, + 0x0017793C, 0x00952F3F, 0x004226BC, 0x00918239, 0x00468BBA, 0x00C4DDB9, 0x0013D43A, + 0x0099D834, 0x004ED1B7, 0x00CC87B4, 0x001B8E37, 0x00C82AB2, 0x001F2331, 0x009D7532, + 0x004A7CB1, 0x00896C2E, 0x005E65AD, 0x00DC33AE, 0x000B3A2D, 0x00D89EA8, 0x000F972B, + 0x008DC128, 0x005AC8AB, 0x00D0C4A5, 0x0007CD26, 0x00859B25, 0x005292A6, 0x00813623, + 0x00563FA0, 0x00D469A3, 0x00036020, 0x0080A0D5, 0x0057A956, 0x00D5FF55, 0x0002F6D6, + 0x00D15253, 0x00065BD0, 0x00840DD3, 0x00530450, 0x00D9085E, 0x000E01DD, 0x008C57DE, + 0x005B5E5D, 0x0088FAD8, 0x005FF35B, 0x00DDA558, 0x000AACDB, 0x00C9BC44, 0x001EB5C7, + 0x009CE3C4, 0x004BEA47, 0x00984EC2, 0x004F4741, 0x00CD1142, 0x001A18C1, 0x009014CF, + 0x00471D4C, 0x00C54B4F, 0x001242CC, 0x00C1E649, 0x0016EFCA, 0x0094B9C9, 0x0043B04A, + 0x00E9D471, 0x003EDDF2, 0x00BC8BF1, 0x006B8272, 0x00B826F7, 0x006F2F74, 0x00ED7977, + 0x003A70F4, 0x00B07CFA, 0x00677579, 0x00E5237A, 0x00322AF9, 0x00E18E7C, 0x003687FF, + 0x00B4D1FC, 0x0063D87F, 0x00A0C8E0, 0x0077C163, 0x00F59760, 0x00229EE3, 0x00F13A66, + 0x002633E5, 0x00A465E6, 0x00736C65, 0x00F9606B, 0x002E69E8, 0x00AC3FEB, 0x007B3668, + 0x00A892ED, 0x007F9B6E, 0x00FDCD6D, 0x002AC4EE, +}; + +static const uint32_t T3[256] = { + 0x00000000, 0x00520936, 0x00A4126C, 0x00F61B5A, 0x004825D8, 0x001A2CEE, 0x00EC37B4, + 0x00BE3E82, 0x006B0636, 0x00390F00, 0x00CF145A, 0x009D1D6C, 0x002323EE, 0x00712AD8, + 0x00873182, 0x00D538B4, 0x00D60C6C, 0x0084055A, 0x00721E00, 0x00201736, 0x009E29B4, + 0x00CC2082, 0x003A3BD8, 0x006832EE, 0x00BD0A5A, 0x00EF036C, 0x00191836, 0x004B1100, + 0x00F52F82, 0x00A726B4, 0x00513DEE, 0x000334D8, 0x00AC19D8, 0x00FE10EE, 0x00080BB4, + 0x005A0282, 0x00E43C00, 0x00B63536, 0x00402E6C, 0x0012275A, 0x00C71FEE, 0x009516D8, + 0x00630D82, 0x003104B4, 0x008F3A36, 0x00DD3300, 0x002B285A, 0x0079216C, 0x007A15B4, + 0x00281C82, 0x00DE07D8, 0x008C0EEE, 0x0032306C, 0x0060395A, 0x00962200, 0x00C42B36, + 0x00111382, 0x00431AB4, 0x00B501EE, 0x00E708D8, 0x0059365A, 0x000B3F6C, 0x00FD2436, + 0x00AF2D00, 0x00A37F36, 0x00F17600, 0x00076D5A, 0x0055646C, 0x00EB5AEE, 0x00B953D8, + 0x004F4882, 0x001D41B4, 0x00C87900, 0x009A7036, 0x006C6B6C, 0x003E625A, 0x00805CD8, + 0x00D255EE, 0x00244EB4, 0x00764782, 0x0075735A, 0x00277A6C, 0x00D16136, 0x00836800, + 0x003D5682, 0x006F5FB4, 0x009944EE, 0x00CB4DD8, 0x001E756C, 0x004C7C5A, 0x00BA6700, + 0x00E86E36, 0x005650B4, 0x00045982, 0x00F242D8, 0x00A04BEE, 0x000F66EE, 0x005D6FD8, + 0x00AB7482, 0x00F97DB4, 0x00474336, 0x00154A00, 0x00E3515A, 0x00B1586C, 0x006460D8, + 0x003669EE, 0x00C072B4, 0x00927B82, 0x002C4500, 0x007E4C36, 0x0088576C, 0x00DA5E5A, + 0x00D96A82, 0x008B63B4, 0x007D78EE, 0x002F71D8, 0x00914F5A, 0x00C3466C, 0x00355D36, + 0x00675400, 0x00B26CB4, 0x00E06582, 0x00167ED8, 0x004477EE, 0x00FA496C, 0x00A8405A, + 0x005E5B00, 0x000C5236, 0x0046FF6C, 0x0014F65A, 0x00E2ED00, 0x00B0E436, 0x000EDAB4, + 0x005CD382, 0x00AAC8D8, 0x00F8C1EE, 0x002DF95A, 0x007FF06C, 0x0089EB36, 0x00DBE200, + 0x0065DC82, 0x0037D5B4, 0x00C1CEEE, 0x0093C7D8, 0x0090F300, 0x00C2FA36, 0x0034E16C, + 0x0066E85A, 0x00D8D6D8, 0x008ADFEE, 0x007CC4B4, 0x002ECD82, 0x00FBF536, 0x00A9FC00, + 0x005FE75A, 0x000DEE6C, 0x00B3D0EE, 0x00E1D9D8, 0x0017C282, 0x0045CBB4, 0x00EAE6B4, + 0x00B8EF82, 0x004EF4D8, 0x001CFDEE, 0x00A2C36C, 0x00F0CA5A, 0x0006D100, 0x0054D836, + 0x0081E082, 0x00D3E9B4, 0x0025F2EE, 0x0077FBD8, 0x00C9C55A, 0x009BCC6C, 0x006DD736, + 0x003FDE00, 0x003CEAD8, 0x006EE3EE, 0x0098F8B4, 0x00CAF182, 0x0074CF00, 0x0026C636, + 0x00D0DD6C, 0x0082D45A, 0x0057ECEE, 0x0005E5D8, 0x00F3FE82, 0x00A1F7B4, 0x001FC936, + 0x004DC000, 0x00BBDB5A, 0x00E9D26C, 0x00E5805A, 0x00B7896C, 0x00419236, 0x00139B00, + 0x00ADA582, 0x00FFACB4, 0x0009B7EE, 0x005BBED8, 0x008E866C, 0x00DC8F5A, 0x002A9400, + 0x00789D36, 0x00C6A3B4, 0x0094AA82, 0x0062B1D8, 0x0030B8EE, 0x00338C36, 0x00618500, + 0x00979E5A, 0x00C5976C, 0x007BA9EE, 0x0029A0D8, 0x00DFBB82, 0x008DB2B4, 0x00588A00, + 0x000A8336, 0x00FC986C, 0x00AE915A, 0x0010AFD8, 0x0042A6EE, 0x00B4BDB4, 0x00E6B482, + 0x00499982, 0x001B90B4, 0x00ED8BEE, 0x00BF82D8, 0x0001BC5A, 0x0053B56C, 0x00A5AE36, + 0x00F7A700, 0x00229FB4, 0x00709682, 0x00868DD8, 0x00D484EE, 0x006ABA6C, 0x0038B35A, + 0x00CEA800, 0x009CA136, 0x009F95EE, 0x00CD9CD8, 0x003B8782, 0x00698EB4, 0x00D7B036, + 0x0085B900, 0x0073A25A, 0x0021AB6C, 0x00F493D8, 0x00A69AEE, 0x005081B4, 0x00028882, + 0x00BCB600, 0x00EEBF36, 0x0018A46C, 0x004AAD5A}; + +#define CRC24_FAST_INIT 0xce04b7L + +static inline uint32_t +process8(uint32_t crc, uint8_t data) +{ + return (crc >> 8) ^ T0[(crc & 0xff) ^ data]; +} + +/* + * Process 4 bytes in one go + */ +static inline uint32_t +process32(uint32_t crc, uint32_t word) +{ + crc ^= word; + crc = T3[crc & 0xff] ^ T2[((crc >> 8) & 0xff)] ^ T1[((crc >> 16) & 0xff)] ^ + T0[(crc >> 24) & 0xff]; + return crc; +} + +static uint32_t +crc24_update(uint32_t crc, const uint8_t *in, size_t length) +{ + uint32_t d0, d1, d2, d3; + + while (length >= 16) { + LOAD32LE(d0, &in[0]); + LOAD32LE(d1, &in[4]); + LOAD32LE(d2, &in[8]); + LOAD32LE(d3, &in[12]); + + crc = process32(crc, d0); + crc = process32(crc, d1); + crc = process32(crc, d2); + crc = process32(crc, d3); + + in += 16; + length -= 16; + } + + while (length--) { + crc = process8(crc, *in++); + } + + return crc & 0xffffff; +} + +/* Swap endianness of 32-bit value */ +#if defined(__GNUC__) || defined(__clang__) +#define BSWAP32(x) __builtin_bswap32(x) +#else +#define BSWAP32(x) \ + ((x & 0x000000FF) << 24 | (x & 0x0000FF00) << 8 | (x & 0x00FF0000) >> 8 | \ + (x & 0xFF000000) >> 24) +#endif + +static uint32_t +crc24_final(uint32_t crc) +{ + return (BSWAP32(crc) >> 8); +} + +namespace rnp { + +CRC24_RNP::CRC24_RNP() +{ + state_ = CRC24_FAST_INIT; +} + +CRC24_RNP::~CRC24_RNP() +{ +} + +std::unique_ptr<CRC24_RNP> +CRC24_RNP::create() +{ + return std::unique_ptr<CRC24_RNP>(new CRC24_RNP()); +} + +void +CRC24_RNP::add(const void *buf, size_t len) +{ + state_ = crc24_update(state_, static_cast<const uint8_t *>(buf), len); +} + +std::array<uint8_t, 3> +CRC24_RNP::finish() +{ + uint32_t crc_fin = crc24_final(state_); + state_ = 0; + std::array<uint8_t, 3> res; + res[0] = (crc_fin >> 16) & 0xff; + res[1] = (crc_fin >> 8) & 0xff; + res[2] = crc_fin & 0xff; + return res; +} + +}; // namespace rnp diff --git a/src/lib/crypto/hash_crc24.hpp b/src/lib/crypto/hash_crc24.hpp new file mode 100644 index 0000000..a73a466 --- /dev/null +++ b/src/lib/crypto/hash_crc24.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_HASH_CRC24_HPP_ +#define CRYPTO_HASH_CRC24_HPP_ + +#include "hash.hpp" + +namespace rnp { +class CRC24_RNP : public CRC24 { + uint32_t state_; + CRC24_RNP(); + + public: + virtual ~CRC24_RNP(); + + static std::unique_ptr<CRC24_RNP> create(); + + void add(const void *buf, size_t len) override; + std::array<uint8_t, 3> finish() override; +}; +} // namespace rnp + +#endif
\ No newline at end of file diff --git a/src/lib/crypto/hash_ossl.cpp b/src/lib/crypto/hash_ossl.cpp new file mode 100644 index 0000000..37b7546 --- /dev/null +++ b/src/lib/crypto/hash_ossl.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2021-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "hash_ossl.hpp" +#include <stdio.h> +#include <memory> +#include <cassert> +#include <openssl/err.h> +#include "config.h" +#include "types.h" +#include "utils.h" +#include "str-utils.h" +#include "defaults.h" + +static const id_str_pair openssl_alg_map[] = { + {PGP_HASH_MD5, "md5"}, + {PGP_HASH_SHA1, "sha1"}, + {PGP_HASH_RIPEMD, "ripemd160"}, + {PGP_HASH_SHA256, "sha256"}, + {PGP_HASH_SHA384, "sha384"}, + {PGP_HASH_SHA512, "sha512"}, + {PGP_HASH_SHA224, "sha224"}, + {PGP_HASH_SM3, "sm3"}, + {PGP_HASH_SHA3_256, "sha3-256"}, + {PGP_HASH_SHA3_512, "sha3-512"}, + {0, NULL}, +}; + +namespace rnp { +Hash_OpenSSL::Hash_OpenSSL(pgp_hash_alg_t alg) : Hash(alg) +{ + const char * hash_name = Hash_OpenSSL::name_backend(alg); + const EVP_MD *hash_tp = EVP_get_digestbyname(hash_name); + if (!hash_tp) { + RNP_LOG("Error creating hash object for '%s'", hash_name); + throw rnp_exception(RNP_ERROR_BAD_STATE); + } + fn_ = EVP_MD_CTX_new(); + if (!fn_) { + RNP_LOG("Allocation failure"); + throw rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + int res = EVP_DigestInit_ex(fn_, hash_tp, NULL); + if (res != 1) { + RNP_LOG("Digest initializataion error %d : %lu", res, ERR_peek_last_error()); + EVP_MD_CTX_free(fn_); + throw rnp_exception(RNP_ERROR_BAD_STATE); + } + assert(size_ == (size_t) EVP_MD_size(hash_tp)); +} + +Hash_OpenSSL::Hash_OpenSSL(const Hash_OpenSSL &src) : Hash(src.alg_) +{ + if (!src.fn_) { + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + fn_ = EVP_MD_CTX_new(); + if (!fn_) { + RNP_LOG("Allocation failure"); + throw rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + + int res = EVP_MD_CTX_copy(fn_, src.fn_); + if (res != 1) { + RNP_LOG("Digest copying error %d: %lu", res, ERR_peek_last_error()); + EVP_MD_CTX_free(fn_); + throw rnp_exception(RNP_ERROR_BAD_STATE); + } +} + +std::unique_ptr<Hash_OpenSSL> +Hash_OpenSSL::create(pgp_hash_alg_t alg) +{ + return std::unique_ptr<Hash_OpenSSL>(new Hash_OpenSSL(alg)); +} + +std::unique_ptr<Hash> +Hash_OpenSSL::clone() const +{ + return std::unique_ptr<Hash>(new Hash_OpenSSL(*this)); +} + +void +Hash_OpenSSL::add(const void *buf, size_t len) +{ + if (!fn_) { + throw rnp_exception(RNP_ERROR_NULL_POINTER); + } + int res = EVP_DigestUpdate(fn_, buf, len); + if (res != 1) { + RNP_LOG("Digest updating error %d: %lu", res, ERR_peek_last_error()); + throw rnp_exception(RNP_ERROR_GENERIC); + } +} + +size_t +Hash_OpenSSL::finish(uint8_t *digest) +{ + if (!fn_) { + return 0; + } + int res = digest ? EVP_DigestFinal_ex(fn_, digest, NULL) : 1; + EVP_MD_CTX_free(fn_); + fn_ = NULL; + if (res != 1) { + RNP_LOG("Digest finalization error %d: %lu", res, ERR_peek_last_error()); + return 0; + } + + size_t outsz = size_; + size_ = 0; + return outsz; +} + +Hash_OpenSSL::~Hash_OpenSSL() +{ + if (!fn_) { + return; + } + EVP_MD_CTX_free(fn_); +} + +const char * +Hash_OpenSSL::name_backend(pgp_hash_alg_t alg) +{ + return id_str_pair::lookup(openssl_alg_map, alg); +} +} // namespace rnp diff --git a/src/lib/crypto/hash_ossl.hpp b/src/lib/crypto/hash_ossl.hpp new file mode 100644 index 0000000..95b365b --- /dev/null +++ b/src/lib/crypto/hash_ossl.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_HASH_OSSL_HPP_ +#define CRYPTO_HASH_OSSL_HPP_ + +#include "hash.hpp" +#include <openssl/evp.h> + +namespace rnp { +class Hash_OpenSSL : public Hash { + private: + EVP_MD_CTX *fn_; + + Hash_OpenSSL(pgp_hash_alg_t alg); + Hash_OpenSSL(const Hash_OpenSSL &src); + + public: + virtual ~Hash_OpenSSL(); + + static std::unique_ptr<Hash_OpenSSL> create(pgp_hash_alg_t alg); + std::unique_ptr<Hash> clone() const override; + + void add(const void *buf, size_t len) override; + size_t finish(uint8_t *digest = NULL) override; + + static const char *name_backend(pgp_hash_alg_t alg); +}; + +} // namespace rnp + +#endif
\ No newline at end of file diff --git a/src/lib/crypto/hash_sha1cd.cpp b/src/lib/crypto/hash_sha1cd.cpp new file mode 100644 index 0000000..863591e --- /dev/null +++ b/src/lib/crypto/hash_sha1cd.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2021-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <cassert> +#include "logging.h" +#include "hash_sha1cd.hpp" + +namespace rnp { +Hash_SHA1CD::Hash_SHA1CD() : Hash(PGP_HASH_SHA1) +{ + assert(size_ == 20); + SHA1DCInit(&ctx_); +} + +Hash_SHA1CD::Hash_SHA1CD(const Hash_SHA1CD &src) : Hash(PGP_HASH_SHA1) +{ + ctx_ = src.ctx_; +} + +Hash_SHA1CD::~Hash_SHA1CD() +{ +} + +std::unique_ptr<Hash_SHA1CD> +Hash_SHA1CD::create() +{ + return std::unique_ptr<Hash_SHA1CD>(new Hash_SHA1CD()); +} + +std::unique_ptr<Hash> +Hash_SHA1CD::clone() const +{ + return std::unique_ptr<Hash>(new Hash_SHA1CD(*this)); +} + +/* This produces runtime error: load of misaligned address 0x60d0000030a9 for type 'const + * uint32_t' (aka 'const unsigned int'), which requires 4 byte alignment */ +#if defined(__clang__) +__attribute__((no_sanitize("undefined"))) +#endif +void +Hash_SHA1CD::add(const void *buf, size_t len) +{ + SHA1DCUpdate(&ctx_, (const char *) buf, len); +} + +#if defined(__clang__) +__attribute__((no_sanitize("undefined"))) +#endif +size_t +Hash_SHA1CD::finish(uint8_t *digest) +{ + unsigned char fixed_digest[20]; + int res = SHA1DCFinal(fixed_digest, &ctx_); + if (res && digest) { + /* Show warning only if digest is non-null */ + RNP_LOG("Warning! SHA1 collision detected and mitigated."); + } + if (res) { + throw rnp_exception(RNP_ERROR_BAD_STATE); + } + if (digest) { + memcpy(digest, fixed_digest, 20); + } + return 20; +} + +} // namespace rnp diff --git a/src/lib/crypto/hash_sha1cd.hpp b/src/lib/crypto/hash_sha1cd.hpp new file mode 100644 index 0000000..523bbe0 --- /dev/null +++ b/src/lib/crypto/hash_sha1cd.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_HASH_SHA1CD_HPP_ +#define CRYPTO_HASH_SHA1CD_HPP_ + +#include "hash.hpp" +#include "sha1cd/sha1.h" + +namespace rnp { +class Hash_SHA1CD : public Hash { + private: + SHA1_CTX ctx_; + + Hash_SHA1CD(); + Hash_SHA1CD(const Hash_SHA1CD &src); + + public: + virtual ~Hash_SHA1CD(); + + static std::unique_ptr<Hash_SHA1CD> create(); + std::unique_ptr<Hash> clone() const override; + + void add(const void *buf, size_t len) override; + size_t finish(uint8_t *digest = NULL) override; +}; + +} // namespace rnp +#endif diff --git a/src/lib/crypto/mem.cpp b/src/lib/crypto/mem.cpp new file mode 100644 index 0000000..bf54aa6 --- /dev/null +++ b/src/lib/crypto/mem.cpp @@ -0,0 +1,68 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <cstdio> +#include "mem.h" +#include "logging.h" +#include <botan/ffi.h> + +void +secure_clear(void *vp, size_t size) +{ + botan_scrub_mem(vp, size); +} + +namespace rnp { + +bool +hex_encode(const uint8_t *buf, size_t buf_len, char *hex, size_t hex_len, hex_format_t format) +{ + uint32_t flags = format == HEX_LOWERCASE ? BOTAN_FFI_HEX_LOWER_CASE : 0; + + if (hex_len < (buf_len * 2 + 1)) { + return false; + } + hex[buf_len * 2] = '\0'; + return botan_hex_encode(buf, buf_len, hex, flags) == 0; +} + +size_t +hex_decode(const char *hex, uint8_t *buf, size_t buf_len) +{ + size_t hexlen = strlen(hex); + + /* check for 0x prefix */ + if ((hexlen >= 2) && (hex[0] == '0') && ((hex[1] == 'x') || (hex[1] == 'X'))) { + hex += 2; + hexlen -= 2; + } + if (botan_hex_decode(hex, hexlen, buf, &buf_len) != 0) { + RNP_LOG("Hex decode failed on string: %s", hex); + return 0; + } + return buf_len; +} +} // namespace rnp
\ No newline at end of file diff --git a/src/lib/crypto/mem.h b/src/lib/crypto/mem.h new file mode 100644 index 0000000..fe574da --- /dev/null +++ b/src/lib/crypto/mem.h @@ -0,0 +1,158 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_MEM_H_ +#define CRYPTO_MEM_H_ + +#include "config.h" +#include <array> +#include <vector> +#if defined(CRYPTO_BACKEND_BOTAN) +#include <botan/secmem.h> +#include <botan/ffi.h> +#elif defined(CRYPTO_BACKEND_OPENSSL) +#include <openssl/crypto.h> +#endif + +namespace rnp { + +#if defined(CRYPTO_BACKEND_BOTAN) +template <typename T> using secure_vector = Botan::secure_vector<T>; +#elif defined(CRYPTO_BACKEND_OPENSSL) +template <typename T> class ossl_allocator { + public: + static_assert(std::is_integral<T>::value, "T must be integral type"); + + typedef T value_type; + typedef std::size_t size_type; + + ossl_allocator() noexcept = default; + ossl_allocator(const ossl_allocator &) noexcept = default; + ossl_allocator &operator=(const ossl_allocator &) noexcept = default; + ~ossl_allocator() noexcept = default; + + template <typename U> ossl_allocator(const ossl_allocator<U> &) noexcept + { + } + + T * + allocate(std::size_t n) + { + if (!n) { + return nullptr; + } + + /* attempt to use OpenSSL secure alloc */ + T *ptr = static_cast<T *>(OPENSSL_secure_zalloc(n * sizeof(T))); + if (ptr) { + return ptr; + } + /* fallback to std::alloc if failed */ + ptr = static_cast<T *>(std::calloc(n, sizeof(T))); + if (!ptr) + throw std::bad_alloc(); + return ptr; + } + + void + deallocate(T *p, std::size_t n) + { + if (!p) { + return; + } + if (CRYPTO_secure_allocated(p)) { + OPENSSL_secure_clear_free(p, n * sizeof(T)); + return; + } + OPENSSL_cleanse(p, n * sizeof(T)); + std::free(p); + } +}; + +template <typename T> using secure_vector = std::vector<T, ossl_allocator<T> >; +#else +#error Unsupported backend. +#endif + +template <typename T, std::size_t N> struct secure_array { + private: + static_assert(std::is_integral<T>::value, "T must be integer type"); + std::array<T, N> data_; + + public: + secure_array() : data_({0}) + { + } + + T * + data() + { + return &data_[0]; + } + + std::size_t + size() const + { + return data_.size(); + } + + T + operator[](size_t idx) const + { + return data_[idx]; + } + + T & + operator[](size_t idx) + { + return data_[idx]; + } + + ~secure_array() + { +#if defined(CRYPTO_BACKEND_BOTAN) + botan_scrub_mem(&data_[0], sizeof(data_)); +#elif defined(CRYPTO_BACKEND_OPENSSL) + OPENSSL_cleanse(&data_[0], sizeof(data_)); +#else +#error "Unsupported crypto backend." +#endif + } +}; + +typedef enum { HEX_LOWERCASE, HEX_UPPERCASE } hex_format_t; + +bool hex_encode(const uint8_t *buf, + size_t buf_len, + char * hex, + size_t hex_len, + hex_format_t format = HEX_UPPERCASE); +size_t hex_decode(const char *hex, uint8_t *buf, size_t buf_len); +} // namespace rnp + +void secure_clear(void *vp, size_t size); + +#endif // CRYPTO_MEM_H_ diff --git a/src/lib/crypto/mem_ossl.cpp b/src/lib/crypto/mem_ossl.cpp new file mode 100644 index 0000000..e9d6a93 --- /dev/null +++ b/src/lib/crypto/mem_ossl.cpp @@ -0,0 +1,113 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <cstdio> +#include <cstring> +#include "mem.h" +#include "logging.h" +#include <openssl/crypto.h> + +void +secure_clear(void *vp, size_t size) +{ + OPENSSL_cleanse(vp, size); +} + +namespace rnp { + +bool +hex_encode(const uint8_t *buf, size_t buf_len, char *hex, size_t hex_len, hex_format_t format) +{ + if (hex_len < (buf_len * 2 + 1)) { + return false; + } + static const char *hex_low = "0123456789abcdef"; + static const char *hex_up = "0123456789ABCDEF"; + const char * hex_ch = (format == HEX_LOWERCASE) ? hex_low : hex_up; + hex[buf_len * 2] = '\0'; + for (size_t i = 0; i < buf_len; i++) { + hex[i << 1] = hex_ch[buf[i] >> 4]; + hex[(i << 1) + 1] = hex_ch[buf[i] & 0xF]; + } + return true; +} + +static bool +hex_char_decode(const char hex, uint8_t &res) +{ + if ((hex >= '0') && (hex <= '9')) { + res = hex - '0'; + return true; + } + if (hex >= 'a' && hex <= 'f') { + res = hex + 10 - 'a'; + return true; + } + if (hex >= 'A' && hex <= 'F') { + res = hex + 10 - 'A'; + return true; + } + return false; +} + +size_t +hex_decode(const char *hex, uint8_t *buf, size_t buf_len) +{ + size_t hexlen = strlen(hex); + + /* check for 0x prefix */ + if ((hexlen >= 2) && (hex[0] == '0') && ((hex[1] == 'x') || (hex[1] == 'X'))) { + hex += 2; + hexlen -= 2; + } + const char *end = hex + hexlen; + uint8_t * buf_st = buf; + uint8_t * buf_en = buf + buf_len; + while (hex < end) { + /* skip whitespaces */ + if ((*hex < '0') && + ((*hex == ' ') || (*hex == '\t') || (*hex == '\r') || (*hex == '\n'))) { + hex++; + continue; + } + if (hexlen < 2) { + RNP_LOG("Invalid hex string length."); + return 0; + } + uint8_t lo, hi; + if (!hex_char_decode(*hex++, hi) || !hex_char_decode(*hex++, lo)) { + RNP_LOG("Hex decode failed on string: %s", hex); + return 0; + } + if (buf == buf_en) { + return 0; + } + *buf++ = (hi << 4) | lo; + } + return buf - buf_st; +} + +} // namespace rnp diff --git a/src/lib/crypto/mpi.cpp b/src/lib/crypto/mpi.cpp new file mode 100644 index 0000000..4df7eea --- /dev/null +++ b/src/lib/crypto/mpi.cpp @@ -0,0 +1,119 @@ +/*- + * Copyright (c) 2018 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <stdlib.h> +#include "mpi.h" +#include "mem.h" +#include "utils.h" + +size_t +mpi_bits(const pgp_mpi_t *val) +{ + size_t bits = 0; + size_t idx = 0; + uint8_t bt; + + for (idx = 0; (idx < val->len) && !val->mpi[idx]; idx++) + ; + + if (idx < val->len) { + for (bits = (val->len - idx - 1) << 3, bt = val->mpi[idx]; bt; bits++, bt = bt >> 1) + ; + } + + return bits; +} + +size_t +mpi_bytes(const pgp_mpi_t *val) +{ + return val->len; +} + +bool +mem2mpi(pgp_mpi_t *val, const void *mem, size_t len) +{ + if (len > sizeof(val->mpi)) { + return false; + } + + memcpy(val->mpi, mem, len); + val->len = len; + return true; +} + +void +mpi2mem(const pgp_mpi_t *val, void *mem) +{ + memcpy(mem, val->mpi, val->len); +} + +char * +mpi2hex(const pgp_mpi_t *val) +{ + static const char *hexes = "0123456789abcdef"; + char * out; + size_t len; + size_t idx = 0; + + len = mpi_bytes(val); + out = (char *) malloc(len * 2 + 1); + + if (!out) { + return out; + } + + for (size_t i = 0; i < len; i++) { + out[idx++] = hexes[val->mpi[i] >> 4]; + out[idx++] = hexes[val->mpi[i] & 0xf]; + } + out[idx] = '\0'; + return out; +} + +bool +mpi_equal(const pgp_mpi_t *val1, const pgp_mpi_t *val2) +{ + size_t idx1 = 0; + size_t idx2 = 0; + + for (idx1 = 0; (idx1 < val1->len) && !val1->mpi[idx1]; idx1++) + ; + + for (idx2 = 0; (idx2 < val2->len) && !val2->mpi[idx2]; idx2++) + ; + + return ((val1->len - idx1) == (val2->len - idx2) && + !memcmp(val1->mpi + idx1, val2->mpi + idx2, val1->len - idx1)); +} + +void +mpi_forget(pgp_mpi_t *val) +{ + secure_clear(val, sizeof(*val)); + val->len = 0; +} diff --git a/src/lib/crypto/mpi.h b/src/lib/crypto/mpi.h new file mode 100644 index 0000000..f95aeea --- /dev/null +++ b/src/lib/crypto/mpi.h @@ -0,0 +1,58 @@ +/*- + * Copyright (c) 2018 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_MPI_H_ +#define RNP_MPI_H_ + +#include <cstdint> +#include <cstdbool> +#include <cstddef> + +/* 16384 bits should be pretty enough for now */ +#define PGP_MPINT_BITS (16384) +#define PGP_MPINT_SIZE (PGP_MPINT_BITS >> 3) + +/** multi-precision integer, used in signatures and public/secret keys */ +typedef struct pgp_mpi_t { + uint8_t mpi[PGP_MPINT_SIZE]; + size_t len; +} pgp_mpi_t; + +bool mem2mpi(pgp_mpi_t *val, const void *mem, size_t len); + +void mpi2mem(const pgp_mpi_t *val, void *mem); + +char *mpi2hex(const pgp_mpi_t *val); + +size_t mpi_bits(const pgp_mpi_t *val); + +size_t mpi_bytes(const pgp_mpi_t *val); + +bool mpi_equal(const pgp_mpi_t *val1, const pgp_mpi_t *val2); + +void mpi_forget(pgp_mpi_t *val); + +#endif // MPI_H_ diff --git a/src/lib/crypto/ossl_common.h b/src/lib/crypto/ossl_common.h new file mode 100644 index 0000000..b6b7067 --- /dev/null +++ b/src/lib/crypto/ossl_common.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_OSSL_COMMON_H_ +#define RNP_OSSL_COMMON_H_ + +#include <string> +#include "config.h" +#include <openssl/err.h> + +inline const char * +ossl_latest_err() +{ + return ERR_error_string(ERR_peek_last_error(), NULL); +} + +#endif diff --git a/src/lib/crypto/rng.cpp b/src/lib/crypto/rng.cpp new file mode 100644 index 0000000..bf5bfad --- /dev/null +++ b/src/lib/crypto/rng.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <assert.h> +#include <botan/ffi.h> +#include "rng.h" +#include "types.h" + +namespace rnp { +RNG::RNG(Type type) +{ + if (botan_rng_init(&botan_rng, type == Type::DRBG ? "user" : NULL)) { + throw rnp::rnp_exception(RNP_ERROR_RNG); + } +} + +RNG::~RNG() +{ + (void) botan_rng_destroy(botan_rng); +} + +void +RNG::get(uint8_t *data, size_t len) +{ + if (botan_rng_get(botan_rng, data, len)) { + // This should never happen + throw rnp::rnp_exception(RNP_ERROR_RNG); + } +} + +struct botan_rng_struct * +RNG::handle() +{ + return botan_rng; +} +} // namespace rnp diff --git a/src/lib/crypto/rng.h b/src/lib/crypto/rng.h new file mode 100644 index 0000000..f452bd9 --- /dev/null +++ b/src/lib/crypto/rng.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_RNG_H_ +#define RNP_RNG_H_ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include "config.h" + +#ifdef CRYPTO_BACKEND_BOTAN +typedef struct botan_rng_struct *botan_rng_t; +#endif + +namespace rnp { +class RNG { + private: +#ifdef CRYPTO_BACKEND_BOTAN + struct botan_rng_struct *botan_rng; +#endif + public: + enum Type { DRBG, System }; + /** + * @brief Construct a new RNG object. + * Note: OpenSSL uses own global RNG, so this class is not needed there and left + * only for code-level compatibility. + * + * @param type indicates which random generator to initialize. + * Possible values for Botan backend: + * - DRBG will initialize HMAC_DRBG, this generator is initialized on-demand + * (when used for the first time) + * - SYSTEM will initialize /dev/(u)random + */ + RNG(Type type = Type::DRBG); + ~RNG(); + /** + * @brief Get randoom bytes. + * + * @param data buffer where data should be stored. Cannot be NULL. + * @param len number of bytes required. + */ + void get(uint8_t *data, size_t len); +#ifdef CRYPTO_BACKEND_BOTAN + /** + * @brief Returns internal handle to botan rng. Returned + * handle is always initialized. In case of + * internal error NULL is returned + */ + struct botan_rng_struct *handle(); +#endif +}; +} // namespace rnp + +#endif // RNP_RNG_H_ diff --git a/src/lib/crypto/rng_ossl.cpp b/src/lib/crypto/rng_ossl.cpp new file mode 100644 index 0000000..4ebcc95 --- /dev/null +++ b/src/lib/crypto/rng_ossl.cpp @@ -0,0 +1,48 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <assert.h> +#include <openssl/rand.h> +#include "rng.h" +#include "types.h" + +namespace rnp { +RNG::RNG(Type type) +{ +} + +RNG::~RNG() +{ +} + +void +RNG::get(uint8_t *data, size_t len) +{ + if (RAND_bytes(data, len) != 1) { + throw rnp::rnp_exception(RNP_ERROR_RNG); + } +} +} // namespace rnp diff --git a/src/lib/crypto/rsa.cpp b/src/lib/crypto/rsa.cpp new file mode 100644 index 0000000..f7ddefe --- /dev/null +++ b/src/lib/crypto/rsa.cpp @@ -0,0 +1,419 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Alistair Crooks (agc@NetBSD.org) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** \file + */ +#include <string> +#include <cstring> +#include <botan/ffi.h> +#include "hash_botan.hpp" +#include "crypto/rsa.h" +#include "config.h" +#include "utils.h" +#include "bn.h" + +rnp_result_t +rsa_validate_key(rnp::RNG *rng, const pgp_rsa_key_t *key, bool secret) +{ + bignum_t * n = NULL; + bignum_t * e = NULL; + bignum_t * p = NULL; + bignum_t * q = NULL; + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + + /* load and check public key part */ + if (!(n = mpi2bn(&key->n)) || !(e = mpi2bn(&key->e))) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (botan_pubkey_load_rsa(&bpkey, BN_HANDLE_PTR(n), BN_HANDLE_PTR(e)) != 0) { + goto done; + } + + if (botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + /* load and check secret key part */ + if (!(p = mpi2bn(&key->p)) || !(q = mpi2bn(&key->q))) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + /* p and q are reversed from normal usage in PGP */ + if (botan_privkey_load_rsa(&bskey, BN_HANDLE_PTR(q), BN_HANDLE_PTR(p), BN_HANDLE_PTR(e))) { + goto done; + } + + if (botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_pubkey_destroy(bpkey); + botan_privkey_destroy(bskey); + bn_free(n); + bn_free(e); + bn_free(p); + bn_free(q); + return ret; +} + +static bool +rsa_load_public_key(botan_pubkey_t *bkey, const pgp_rsa_key_t *key) +{ + bignum_t *n = NULL; + bignum_t *e = NULL; + bool res = false; + + *bkey = NULL; + n = mpi2bn(&key->n); + e = mpi2bn(&key->e); + + if (!n || !e) { + RNP_LOG("out of memory"); + goto done; + } + + res = !botan_pubkey_load_rsa(bkey, BN_HANDLE_PTR(n), BN_HANDLE_PTR(e)); +done: + bn_free(n); + bn_free(e); + return res; +} + +static bool +rsa_load_secret_key(botan_privkey_t *bkey, const pgp_rsa_key_t *key) +{ + bignum_t *p = NULL; + bignum_t *q = NULL; + bignum_t *e = NULL; + bool res = false; + + *bkey = NULL; + p = mpi2bn(&key->p); + q = mpi2bn(&key->q); + e = mpi2bn(&key->e); + + if (!p || !q || !e) { + RNP_LOG("out of memory"); + goto done; + } + + /* p and q are reversed from normal usage in PGP */ + res = !botan_privkey_load_rsa(bkey, BN_HANDLE_PTR(q), BN_HANDLE_PTR(p), BN_HANDLE_PTR(e)); +done: + bn_free(p); + bn_free(q); + bn_free(e); + return res; +} + +rnp_result_t +rsa_encrypt_pkcs1(rnp::RNG * rng, + pgp_rsa_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_rsa_key_t *key) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + botan_pubkey_t rsa_key = NULL; + botan_pk_op_encrypt_t enc_op = NULL; + + if (!rsa_load_public_key(&rsa_key, key)) { + RNP_LOG("failed to load key"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (botan_pk_op_encrypt_create(&enc_op, rsa_key, "PKCS1v15", 0) != 0) { + goto done; + } + + out->m.len = sizeof(out->m.mpi); + if (botan_pk_op_encrypt(enc_op, rng->handle(), out->m.mpi, &out->m.len, in, in_len)) { + out->m.len = 0; + goto done; + } + ret = RNP_SUCCESS; +done: + botan_pk_op_encrypt_destroy(enc_op); + botan_pubkey_destroy(rsa_key); + return ret; +} + +rnp_result_t +rsa_verify_pkcs1(const pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t * key) +{ + char padding_name[64] = {0}; + botan_pubkey_t rsa_key = NULL; + botan_pk_op_verify_t verify_op = NULL; + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + + if (!rsa_load_public_key(&rsa_key, key)) { + RNP_LOG("failed to load key"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + snprintf(padding_name, + sizeof(padding_name), + "EMSA-PKCS1-v1_5(Raw,%s)", + rnp::Hash_Botan::name_backend(hash_alg)); + + if (botan_pk_op_verify_create(&verify_op, rsa_key, padding_name, 0) != 0) { + goto done; + } + + if (botan_pk_op_verify_update(verify_op, hash, hash_len) != 0) { + goto done; + } + + if (botan_pk_op_verify_finish(verify_op, sig->s.mpi, sig->s.len) != 0) { + goto done; + } + + ret = RNP_SUCCESS; +done: + botan_pk_op_verify_destroy(verify_op); + botan_pubkey_destroy(rsa_key); + return ret; +} + +rnp_result_t +rsa_sign_pkcs1(rnp::RNG * rng, + pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t *key) +{ + char padding_name[64] = {0}; + botan_privkey_t rsa_key; + botan_pk_op_sign_t sign_op; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (mpi_bytes(&key->q) == 0) { + RNP_LOG("private key not set"); + return ret; + } + + if (!rsa_load_secret_key(&rsa_key, key)) { + RNP_LOG("failed to load key"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + snprintf(padding_name, + sizeof(padding_name), + "EMSA-PKCS1-v1_5(Raw,%s)", + rnp::Hash_Botan::name_backend(hash_alg)); + + if (botan_pk_op_sign_create(&sign_op, rsa_key, padding_name, 0) != 0) { + goto done; + } + + if (botan_pk_op_sign_update(sign_op, hash, hash_len)) { + goto done; + } + + sig->s.len = sizeof(sig->s.mpi); + if (botan_pk_op_sign_finish(sign_op, rng->handle(), sig->s.mpi, &sig->s.len)) { + goto done; + } + + ret = RNP_SUCCESS; +done: + botan_pk_op_sign_destroy(sign_op); + botan_privkey_destroy(rsa_key); + return ret; +} + +rnp_result_t +rsa_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_rsa_encrypted_t *in, + const pgp_rsa_key_t * key) +{ + botan_privkey_t rsa_key = NULL; + botan_pk_op_decrypt_t decrypt_op = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (mpi_bytes(&key->q) == 0) { + RNP_LOG("private key not set"); + return ret; + } + + if (!rsa_load_secret_key(&rsa_key, key)) { + RNP_LOG("failed to load key"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (botan_pk_op_decrypt_create(&decrypt_op, rsa_key, "PKCS1v15", 0)) { + goto done; + } + + *out_len = PGP_MPINT_SIZE; + if (botan_pk_op_decrypt(decrypt_op, out, out_len, in->m.mpi, in->m.len)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_privkey_destroy(rsa_key); + botan_pk_op_decrypt_destroy(decrypt_op); + return ret; +} + +rnp_result_t +rsa_generate(rnp::RNG *rng, pgp_rsa_key_t *key, size_t numbits) +{ + if ((numbits < 1024) || (numbits > PGP_MPINT_BITS)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + botan_privkey_t rsa_key = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + int cmp; + bignum_t * n = bn_new(); + bignum_t * e = bn_new(); + bignum_t * p = bn_new(); + bignum_t * q = bn_new(); + bignum_t * d = bn_new(); + bignum_t * u = bn_new(); + + if (!n || !e || !p || !q || !d || !u) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + + if (botan_privkey_create( + &rsa_key, "RSA", std::to_string(numbits).c_str(), rng->handle())) { + goto end; + } + + if (botan_privkey_check_key(rsa_key, rng->handle(), 1) != 0) { + goto end; + } + + if (botan_privkey_get_field(BN_HANDLE_PTR(n), rsa_key, "n") || + botan_privkey_get_field(BN_HANDLE_PTR(e), rsa_key, "e") || + botan_privkey_get_field(BN_HANDLE_PTR(d), rsa_key, "d") || + botan_privkey_get_field(BN_HANDLE_PTR(p), rsa_key, "p") || + botan_privkey_get_field(BN_HANDLE_PTR(q), rsa_key, "q")) { + goto end; + } + + /* RFC 4880, 5.5.3 tells that p < q. GnuPG relies on this. */ + (void) botan_mp_cmp(&cmp, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q)); + if (cmp > 0) { + (void) botan_mp_swap(BN_HANDLE_PTR(p), BN_HANDLE_PTR(q)); + } + + if (botan_mp_mod_inverse(BN_HANDLE_PTR(u), BN_HANDLE_PTR(p), BN_HANDLE_PTR(q)) != 0) { + RNP_LOG("Error computing RSA u param"); + ret = RNP_ERROR_BAD_STATE; + goto end; + } + + bn2mpi(n, &key->n); + bn2mpi(e, &key->e); + bn2mpi(p, &key->p); + bn2mpi(q, &key->q); + bn2mpi(d, &key->d); + bn2mpi(u, &key->u); + + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(rsa_key); + bn_free(n); + bn_free(e); + bn_free(p); + bn_free(q); + bn_free(d); + bn_free(u); + return ret; +} diff --git a/src/lib/crypto/rsa.h b/src/lib/crypto/rsa.h new file mode 100644 index 0000000..6b1b615 --- /dev/null +++ b/src/lib/crypto/rsa.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_RSA_H_ +#define RNP_RSA_H_ + +#include <rnp/rnp_def.h> +#include <repgp/repgp_def.h> +#include "crypto/rng.h" +#include "crypto/mpi.h" + +typedef struct pgp_rsa_key_t { + pgp_mpi_t n; + pgp_mpi_t e; + /* secret mpis */ + pgp_mpi_t d; + pgp_mpi_t p; + pgp_mpi_t q; + pgp_mpi_t u; +} pgp_rsa_key_t; + +typedef struct pgp_rsa_signature_t { + pgp_mpi_t s; +} pgp_rsa_signature_t; + +typedef struct pgp_rsa_encrypted_t { + pgp_mpi_t m; +} pgp_rsa_encrypted_t; + +/* + * RSA encrypt/decrypt + */ + +rnp_result_t rsa_validate_key(rnp::RNG *rng, const pgp_rsa_key_t *key, bool secret); + +rnp_result_t rsa_generate(rnp::RNG *rng, pgp_rsa_key_t *key, size_t numbits); + +rnp_result_t rsa_encrypt_pkcs1(rnp::RNG * rng, + pgp_rsa_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_rsa_key_t *key); + +rnp_result_t rsa_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_rsa_encrypted_t *in, + const pgp_rsa_key_t * key); + +rnp_result_t rsa_verify_pkcs1(const pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t * key); + +rnp_result_t rsa_sign_pkcs1(rnp::RNG * rng, + pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t *key); + +#endif diff --git a/src/lib/crypto/rsa_ossl.cpp b/src/lib/crypto/rsa_ossl.cpp new file mode 100644 index 0000000..24cff29 --- /dev/null +++ b/src/lib/crypto/rsa_ossl.cpp @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2021-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string> +#include <cstring> +#include <cassert> +#include "crypto/rsa.h" +#include "config.h" +#include "utils.h" +#include "bn.h" +#include "ossl_common.h" +#include <openssl/rsa.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#ifdef CRYPTO_BACKEND_OPENSSL3 +#include <openssl/param_build.h> +#include <openssl/core_names.h> +#endif +#include "hash_ossl.hpp" + +#ifndef CRYPTO_BACKEND_OPENSSL3 +static RSA * +rsa_load_public_key(const pgp_rsa_key_t *key) +{ + RSA * rsa = NULL; + bignum_t *n = mpi2bn(&key->n); + bignum_t *e = mpi2bn(&key->e); + + if (!n || !e) { + RNP_LOG("out of memory"); + goto done; + } + rsa = RSA_new(); + if (!rsa) { + RNP_LOG("Out of memory"); + goto done; + } + if (RSA_set0_key(rsa, n, e, NULL) != 1) { + RNP_LOG("Public key load error: %lu", ERR_peek_last_error()); + RSA_free(rsa); + rsa = NULL; + goto done; + } +done: + /* OpenSSL set0 function transfers ownership of bignums */ + if (!rsa) { + bn_free(n); + bn_free(e); + } + return rsa; +} + +static RSA * +rsa_load_secret_key(const pgp_rsa_key_t *key) +{ + RSA * rsa = NULL; + bignum_t *n = mpi2bn(&key->n); + bignum_t *e = mpi2bn(&key->e); + bignum_t *p = mpi2bn(&key->p); + bignum_t *q = mpi2bn(&key->q); + bignum_t *d = mpi2bn(&key->d); + + if (!n || !p || !q || !e || !d) { + RNP_LOG("out of memory"); + goto done; + } + + rsa = RSA_new(); + if (!rsa) { + RNP_LOG("Out of memory"); + goto done; + } + if (RSA_set0_key(rsa, n, e, d) != 1) { + RNP_LOG("Secret key load error: %lu", ERR_peek_last_error()); + RSA_free(rsa); + rsa = NULL; + goto done; + } + /* OpenSSL has p < q, as we do */ + if (RSA_set0_factors(rsa, p, q) != 1) { + RNP_LOG("Factors load error: %lu", ERR_peek_last_error()); + RSA_free(rsa); + rsa = NULL; + goto done; + } +done: + /* OpenSSL set0 function transfers ownership of bignums */ + if (!rsa) { + bn_free(n); + bn_free(p); + bn_free(q); + bn_free(e); + bn_free(d); + } + return rsa; +} + +static EVP_PKEY_CTX * +rsa_init_context(const pgp_rsa_key_t *key, bool secret) +{ + EVP_PKEY *evpkey = EVP_PKEY_new(); + if (!evpkey) { + RNP_LOG("allocation failed"); + return NULL; + } + EVP_PKEY_CTX *ctx = NULL; + RSA * rsakey = secret ? rsa_load_secret_key(key) : rsa_load_public_key(key); + if (!rsakey) { + goto done; + } + if (EVP_PKEY_set1_RSA(evpkey, rsakey) <= 0) { + RNP_LOG("Failed to set key: %lu", ERR_peek_last_error()); + goto done; + } + ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + } +done: + RSA_free(rsakey); + EVP_PKEY_free(evpkey); + return ctx; +} +#else +static OSSL_PARAM * +rsa_bld_params(const pgp_rsa_key_t *key, bool secret) +{ + OSSL_PARAM * params = NULL; + OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + bignum_t * n = mpi2bn(&key->n); + bignum_t * e = mpi2bn(&key->e); + bignum_t * d = NULL; + bignum_t * p = NULL; + bignum_t * q = NULL; + bignum_t * u = NULL; + BN_CTX * bnctx = NULL; + + if (!n || !e || !bld) { + RNP_LOG("Out of memory"); + goto done; + } + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e)) { + RNP_LOG("Failed to push RSA params."); + goto done; + } + if (secret) { + d = mpi2bn(&key->d); + /* As we have u = p^-1 mod q, and qInv = q^-1 mod p, we need to replace one with + * another */ + p = mpi2bn(&key->q); + q = mpi2bn(&key->p); + u = mpi2bn(&key->u); + if (!d || !p || !q || !u) { + goto done; + } + /* We need to calculate exponents manually */ + bnctx = BN_CTX_new(); + if (!bnctx) { + RNP_LOG("Failed to allocate BN_CTX."); + goto done; + } + bignum_t *p1 = BN_CTX_get(bnctx); + bignum_t *q1 = BN_CTX_get(bnctx); + bignum_t *dp = BN_CTX_get(bnctx); + bignum_t *dq = BN_CTX_get(bnctx); + if (!BN_copy(p1, p) || !BN_sub_word(p1, 1) || !BN_copy(q1, q) || !BN_sub_word(q1, 1) || + !BN_mod(dp, d, p1, bnctx) || !BN_mod(dq, d, q1, bnctx)) { + RNP_LOG("Failed to calculate dP or dQ."); + } + /* Push params */ + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_D, d) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR1, p) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR2, q) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, dp) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, dq) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, u)) { + RNP_LOG("Failed to push RSA secret params."); + goto done; + } + } + params = OSSL_PARAM_BLD_to_param(bld); + if (!params) { + RNP_LOG("Failed to build RSA params: %s.", ossl_latest_err()); + } +done: + bn_free(n); + bn_free(e); + bn_free(d); + bn_free(p); + bn_free(q); + bn_free(u); + BN_CTX_free(bnctx); + OSSL_PARAM_BLD_free(bld); + return params; +} + +static EVP_PKEY * +rsa_load_key(const pgp_rsa_key_t *key, bool secret) +{ + /* Build params */ + OSSL_PARAM *params = rsa_bld_params(key, secret); + if (!params) { + return NULL; + } + /* Create context for key creation */ + EVP_PKEY * res = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %s", ossl_latest_err()); + goto done; + } + /* Create key */ + if (EVP_PKEY_fromdata_init(ctx) <= 0) { + RNP_LOG("Failed to initialize key creation: %s", ossl_latest_err()); + goto done; + } + if (EVP_PKEY_fromdata( + ctx, &res, secret ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY, params) <= 0) { + RNP_LOG("Failed to create RSA key: %s", ossl_latest_err()); + } +done: + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return res; +} + +static EVP_PKEY_CTX * +rsa_init_context(const pgp_rsa_key_t *key, bool secret) +{ + EVP_PKEY *pkey = rsa_load_key(key, secret); + if (!pkey) { + return NULL; + } + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %s", ossl_latest_err()); + } + EVP_PKEY_free(pkey); + return ctx; +} +#endif + +rnp_result_t +rsa_validate_key(rnp::RNG *rng, const pgp_rsa_key_t *key, bool secret) +{ +#ifdef CRYPTO_BACKEND_OPENSSL3 + EVP_PKEY_CTX *ctx = rsa_init_context(key, secret); + if (!ctx) { + RNP_LOG("Failed to init context: %s", ossl_latest_err()); + return RNP_ERROR_GENERIC; + } + int res = secret ? EVP_PKEY_pairwise_check(ctx) : EVP_PKEY_public_check(ctx); + if (res <= 0) { + RNP_LOG("Key validation error: %s", ossl_latest_err()); + } + EVP_PKEY_CTX_free(ctx); + return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; +#else + if (secret) { + EVP_PKEY_CTX *ctx = rsa_init_context(key, secret); + if (!ctx) { + RNP_LOG("Failed to init context: %s", ossl_latest_err()); + return RNP_ERROR_GENERIC; + } + int res = EVP_PKEY_check(ctx); + if (res <= 0) { + RNP_LOG("Key validation error: %s", ossl_latest_err()); + } + EVP_PKEY_CTX_free(ctx); + return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; + } + + /* OpenSSL 1.1.1 doesn't have RSA public key check function, so let's do some checks */ + rnp_result_t ret = RNP_ERROR_GENERIC; + bignum_t * n = mpi2bn(&key->n); + bignum_t * e = mpi2bn(&key->e); + if (!n || !e) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + if ((BN_num_bits(n) < 512) || !BN_is_odd(n) || (BN_num_bits(e) < 2) || !BN_is_odd(e)) { + goto done; + } + ret = RNP_SUCCESS; +done: + bn_free(n); + bn_free(e); + return ret; +#endif +} + +static bool +rsa_setup_context(EVP_PKEY_CTX *ctx) +{ + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) { + RNP_LOG("Failed to set padding: %lu", ERR_peek_last_error()); + return false; + } + return true; +} + +static const uint8_t PKCS1_SHA1_ENCODING[15] = { + 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}; + +static bool +rsa_setup_signature_hash(EVP_PKEY_CTX * ctx, + pgp_hash_alg_t hash_alg, + const uint8_t *&enc, + size_t & enc_size) +{ + const char *hash_name = rnp::Hash_OpenSSL::name(hash_alg); + if (!hash_name) { + RNP_LOG("Unknown hash: %d", (int) hash_alg); + return false; + } + const EVP_MD *hash_tp = EVP_get_digestbyname(hash_name); + if (!hash_tp) { + RNP_LOG("Error creating hash object for '%s'", hash_name); + return false; + } + if (EVP_PKEY_CTX_set_signature_md(ctx, hash_tp) <= 0) { + if ((hash_alg != PGP_HASH_SHA1)) { + RNP_LOG("Failed to set digest %s: %s", hash_name, ossl_latest_err()); + return false; + } + enc = &PKCS1_SHA1_ENCODING[0]; + enc_size = sizeof(PKCS1_SHA1_ENCODING); + } else { + enc = NULL; + enc_size = 0; + } + return true; +} + +rnp_result_t +rsa_encrypt_pkcs1(rnp::RNG * rng, + pgp_rsa_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_rsa_key_t *key) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + EVP_PKEY_CTX *ctx = rsa_init_context(key, false); + if (!ctx) { + return ret; + } + if (EVP_PKEY_encrypt_init(ctx) <= 0) { + RNP_LOG("Failed to initialize encryption: %lu", ERR_peek_last_error()); + goto done; + } + if (!rsa_setup_context(ctx)) { + goto done; + } + out->m.len = sizeof(out->m.mpi); + if (EVP_PKEY_encrypt(ctx, out->m.mpi, &out->m.len, in, in_len) <= 0) { + RNP_LOG("Encryption failed: %lu", ERR_peek_last_error()); + out->m.len = 0; + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + return ret; +} + +rnp_result_t +rsa_verify_pkcs1(const pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t * key) +{ + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + EVP_PKEY_CTX *ctx = rsa_init_context(key, false); + if (!ctx) { + return ret; + } + const uint8_t *hash_enc = NULL; + size_t hash_enc_size = 0; + uint8_t hash_enc_buf[PGP_MAX_HASH_SIZE + 32] = {0}; + assert(hash_len + hash_enc_size <= sizeof(hash_enc_buf)); + + if (EVP_PKEY_verify_init(ctx) <= 0) { + RNP_LOG("Failed to initialize verification: %lu", ERR_peek_last_error()); + goto done; + } + if (!rsa_setup_context(ctx) || + !rsa_setup_signature_hash(ctx, hash_alg, hash_enc, hash_enc_size)) { + goto done; + } + /* Check whether we need to workaround on unsupported SHA1 for RSA signature verification + */ + if (hash_enc_size) { + memcpy(hash_enc_buf, hash_enc, hash_enc_size); + memcpy(&hash_enc_buf[hash_enc_size], hash, hash_len); + hash = hash_enc_buf; + hash_len += hash_enc_size; + } + int res; + if (sig->s.len < key->n.len) { + /* OpenSSL doesn't like signatures smaller then N */ + pgp_mpi_t sn; + sn.len = key->n.len; + size_t diff = key->n.len - sig->s.len; + memset(sn.mpi, 0, diff); + memcpy(&sn.mpi[diff], sig->s.mpi, sig->s.len); + res = EVP_PKEY_verify(ctx, sn.mpi, sn.len, hash, hash_len); + } else { + res = EVP_PKEY_verify(ctx, sig->s.mpi, sig->s.len, hash, hash_len); + } + if (res > 0) { + ret = RNP_SUCCESS; + } else { + RNP_LOG("RSA verification failure: %s", ossl_latest_err()); + } +done: + EVP_PKEY_CTX_free(ctx); + return ret; +} + +rnp_result_t +rsa_sign_pkcs1(rnp::RNG * rng, + pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t *key) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + if (mpi_bytes(&key->q) == 0) { + RNP_LOG("private key not set"); + return ret; + } + EVP_PKEY_CTX *ctx = rsa_init_context(key, true); + if (!ctx) { + return ret; + } + const uint8_t *hash_enc = NULL; + size_t hash_enc_size = 0; + uint8_t hash_enc_buf[PGP_MAX_HASH_SIZE + 32] = {0}; + assert(hash_len + hash_enc_size <= sizeof(hash_enc_buf)); + if (EVP_PKEY_sign_init(ctx) <= 0) { + RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error()); + goto done; + } + if (!rsa_setup_context(ctx) || + !rsa_setup_signature_hash(ctx, hash_alg, hash_enc, hash_enc_size)) { + goto done; + } + /* Check whether we need to workaround on unsupported SHA1 for RSA signature verification + */ + if (hash_enc_size) { + memcpy(hash_enc_buf, hash_enc, hash_enc_size); + memcpy(&hash_enc_buf[hash_enc_size], hash, hash_len); + hash = hash_enc_buf; + hash_len += hash_enc_size; + } + sig->s.len = PGP_MPINT_SIZE; + if (EVP_PKEY_sign(ctx, sig->s.mpi, &sig->s.len, hash, hash_len) <= 0) { + RNP_LOG("Encryption failed: %lu", ERR_peek_last_error()); + sig->s.len = 0; + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + return ret; +} + +rnp_result_t +rsa_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_rsa_encrypted_t *in, + const pgp_rsa_key_t * key) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + if (mpi_bytes(&key->q) == 0) { + RNP_LOG("private key not set"); + return ret; + } + EVP_PKEY_CTX *ctx = rsa_init_context(key, true); + if (!ctx) { + return ret; + } + if (EVP_PKEY_decrypt_init(ctx) <= 0) { + RNP_LOG("Failed to initialize encryption: %lu", ERR_peek_last_error()); + goto done; + } + if (!rsa_setup_context(ctx)) { + goto done; + } + *out_len = PGP_MPINT_SIZE; + if (EVP_PKEY_decrypt(ctx, out, out_len, in->m.mpi, in->m.len) <= 0) { + RNP_LOG("Encryption failed: %lu", ERR_peek_last_error()); + *out_len = 0; + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + return ret; +} + +rnp_result_t +rsa_generate(rnp::RNG *rng, pgp_rsa_key_t *key, size_t numbits) +{ + if ((numbits < 1024) || (numbits > PGP_MPINT_BITS)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + const RSA * rsa = NULL; + EVP_PKEY * pkey = NULL; + EVP_PKEY_CTX * ctx = NULL; + const bignum_t *u = NULL; + BN_CTX * bnctx = NULL; + + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + return ret; + } + if (EVP_PKEY_keygen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, numbits) <= 0) { + RNP_LOG("Failed to set rsa bits: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + RNP_LOG("RSA keygen failed: %lu", ERR_peek_last_error()); + goto done; + } + rsa = EVP_PKEY_get0_RSA(pkey); + if (!rsa) { + RNP_LOG("Failed to retrieve RSA key: %lu", ERR_peek_last_error()); + goto done; + } + if (RSA_check_key(rsa) != 1) { + RNP_LOG("Key validation error: %lu", ERR_peek_last_error()); + goto done; + } + + const bignum_t *n; + const bignum_t *e; + const bignum_t *p; + const bignum_t *q; + const bignum_t *d; + n = RSA_get0_n(rsa); + e = RSA_get0_e(rsa); + d = RSA_get0_d(rsa); + p = RSA_get0_p(rsa); + q = RSA_get0_q(rsa); + if (!n || !e || !d || !p || !q) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + /* OpenSSL doesn't care whether p < q */ + if (BN_cmp(p, q) > 0) { + /* In this case we have u, as iqmp is inverse of q mod p, and we exchange them */ + const bignum_t *tmp = p; + p = q; + q = tmp; + u = RSA_get0_iqmp(rsa); + } else { + /* we need to calculate u, since we need inverse of p mod q, while OpenSSL has inverse + * of q mod p, and doesn't care of p < q */ + bnctx = BN_CTX_new(); + if (!bnctx) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + BN_CTX_start(bnctx); + bignum_t *nu = BN_CTX_get(bnctx); + bignum_t *nq = BN_CTX_get(bnctx); + if (!nu || !nq) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + BN_with_flags(nq, q, BN_FLG_CONSTTIME); + /* calculate inverse of p mod q */ + if (!BN_mod_inverse(nu, p, nq, bnctx)) { + RNP_LOG("Failed to calculate u"); + ret = RNP_ERROR_BAD_STATE; + goto done; + } + u = nu; + } + bn2mpi(n, &key->n); + bn2mpi(e, &key->e); + bn2mpi(p, &key->p); + bn2mpi(q, &key->q); + bn2mpi(d, &key->d); + bn2mpi(u, &key->u); + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + BN_CTX_free(bnctx); + return ret; +} diff --git a/src/lib/crypto/s2k.cpp b/src/lib/crypto/s2k.cpp new file mode 100644 index 0000000..ede7965 --- /dev/null +++ b/src/lib/crypto/s2k.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include "config.h" +#ifndef _MSC_VER +#include <sys/time.h> +#else +#include "uniwin.h" +#endif + +#include "crypto/s2k.h" +#include "defaults.h" +#include "rnp.h" +#include "types.h" +#include "utils.h" +#ifdef CRYPTO_BACKEND_BOTAN +#include <botan/ffi.h> +#include "hash_botan.hpp" +#endif + +bool +pgp_s2k_derive_key(pgp_s2k_t *s2k, const char *password, uint8_t *key, int keysize) +{ + uint8_t *saltptr = NULL; + unsigned iterations = 1; + + switch (s2k->specifier) { + case PGP_S2KS_SIMPLE: + break; + case PGP_S2KS_SALTED: + saltptr = s2k->salt; + break; + case PGP_S2KS_ITERATED_AND_SALTED: + saltptr = s2k->salt; + if (s2k->iterations < 256) { + iterations = pgp_s2k_decode_iterations(s2k->iterations); + } else { + iterations = s2k->iterations; + } + break; + default: + return false; + } + + if (pgp_s2k_iterated(s2k->hash_alg, key, keysize, password, saltptr, iterations)) { + RNP_LOG("s2k failed"); + return false; + } + + return true; +} + +#ifdef CRYPTO_BACKEND_BOTAN +int +pgp_s2k_iterated(pgp_hash_alg_t alg, + uint8_t * out, + size_t output_len, + const char * password, + const uint8_t *salt, + size_t iterations) +{ + char s2k_algo_str[128]; + snprintf(s2k_algo_str, + sizeof(s2k_algo_str), + "OpenPGP-S2K(%s)", + rnp::Hash_Botan::name_backend(alg)); + + return botan_pwdhash(s2k_algo_str, + iterations, + 0, + 0, + out, + output_len, + password, + 0, + salt, + salt ? PGP_SALT_SIZE : 0); +} +#endif + +size_t +pgp_s2k_decode_iterations(uint8_t c) +{ + // See RFC 4880 section 3.7.1.3 + return (16 + (c & 0x0F)) << ((c >> 4) + 6); +} + +size_t +pgp_s2k_round_iterations(size_t iterations) +{ + return pgp_s2k_decode_iterations(pgp_s2k_encode_iterations(iterations)); +} + +uint8_t +pgp_s2k_encode_iterations(size_t iterations) +{ + /* For compatibility, when an S2K specifier is used, the special value + * 254 or 255 is stored in the position where the hash algorithm octet + * would have been in the old data structure. This is then followed + * immediately by a one-octet algorithm identifier, and then by the S2K + * specifier as encoded above. + * 0: secret data is unencrypted (no password) + * 255 or 254: followed by algorithm octet and S2K specifier + * Cipher alg: use Simple S2K algorithm using MD5 hash + * For more info refer to rfc 4880 section 3.7.2.1. + */ + for (uint16_t c = 0; c < 256; ++c) { + // This could be a binary search + if (pgp_s2k_decode_iterations(c) >= iterations) { + return c; + } + } + return 255; +} + +/// Should this function be elsewhere? +static uint64_t +get_timestamp_usec() +{ +#ifndef _MSC_VER + // TODO: Consider clock_gettime + struct timeval tv; + ::gettimeofday(&tv, NULL); + return (static_cast<uint64_t>(tv.tv_sec) * 1000000) + static_cast<uint64_t>(tv.tv_usec); +#else + return GetTickCount64() * 1000; +#endif +} + +size_t +pgp_s2k_compute_iters(pgp_hash_alg_t alg, size_t desired_msec, size_t trial_msec) +{ + if (desired_msec == 0) { + desired_msec = DEFAULT_S2K_MSEC; + } + if (trial_msec == 0) { + trial_msec = DEFAULT_S2K_TUNE_MSEC; + } + + // number of iterations to estimate the number of iterations + // (sorry, cannot tell it better) + const uint8_t NUM_ITERATIONS = 16; + uint64_t duration = 0; + size_t bytes = 0; + try { + for (uint8_t i = 0; i < NUM_ITERATIONS; i++) { + uint64_t start = get_timestamp_usec(); + uint64_t end = start; + auto hash = rnp::Hash::create(alg); + uint8_t buf[8192] = {0}; + while (end - start < trial_msec * 1000ull) { + hash->add(buf, sizeof(buf)); + bytes += sizeof(buf); + end = get_timestamp_usec(); + } + hash->finish(buf); + duration += (end - start); + } + } catch (const std::exception &e) { + RNP_LOG("Failed to hash data: %s", e.what()); + return 0; + } + + const uint8_t MIN_ITERS = 96; + if (duration == 0) { + return pgp_s2k_decode_iterations(MIN_ITERS); + } + + const double bytes_per_usec = static_cast<double>(bytes) / duration; + const double desired_usec = desired_msec * 1000.0; + const double bytes_for_target = bytes_per_usec * desired_usec; + const uint8_t iters = pgp_s2k_encode_iterations(bytes_for_target); + + return pgp_s2k_decode_iterations((iters > MIN_ITERS) ? iters : MIN_ITERS); +} diff --git a/src/lib/crypto/s2k.h b/src/lib/crypto/s2k.h new file mode 100644 index 0000000..c67a773 --- /dev/null +++ b/src/lib/crypto/s2k.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_S2K_H_ +#define RNP_S2K_H_ + +#include <cstdint> +#include "repgp/repgp_def.h" + +typedef struct pgp_s2k_t pgp_s2k_t; + +int pgp_s2k_iterated(pgp_hash_alg_t alg, + uint8_t * out, + size_t output_len, + const char * password, + const uint8_t *salt, + size_t iterations); + +size_t pgp_s2k_decode_iterations(uint8_t encoded_iter); + +uint8_t pgp_s2k_encode_iterations(size_t iterations); + +// Round iterations to nearest representable value +size_t pgp_s2k_round_iterations(size_t iterations); + +size_t pgp_s2k_compute_iters(pgp_hash_alg_t alg, size_t desired_msec, size_t trial_msec); + +/** @brief Derive key from password using the information stored in s2k structure + * @param s2k pointer to s2k structure, filled according to RFC 4880. + * Iterations field may contain encoded ( < 256) or decoded ( > 256) value. + * @param password NULL-terminated password + * @param key buffer to store the derived key, must have at least keysize bytes + * @param keysize number of bytes in the key. + * @return true on success or false otherwise + */ +bool pgp_s2k_derive_key(pgp_s2k_t *s2k, const char *password, uint8_t *key, int keysize); + +#endif diff --git a/src/lib/crypto/s2k_ossl.cpp b/src/lib/crypto/s2k_ossl.cpp new file mode 100644 index 0000000..acf1ca9 --- /dev/null +++ b/src/lib/crypto/s2k_ossl.cpp @@ -0,0 +1,97 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <cstdint> +#include <vector> +#include <algorithm> +#include <openssl/evp.h> +#include "hash.hpp" +#include "s2k.h" +#include "mem.h" +#include "logging.h" + +int +pgp_s2k_iterated(pgp_hash_alg_t alg, + uint8_t * out, + size_t output_len, + const char * password, + const uint8_t *salt, + size_t iterations) +{ + if ((iterations > 1) && !salt) { + RNP_LOG("Iterated S2K mus be salted as well."); + return 1; + } + size_t hash_len = rnp::Hash::size(alg); + if (!hash_len) { + RNP_LOG("Unknown digest: %d", (int) alg); + return 1; + } + try { + size_t pswd_len = strlen(password); + size_t salt_len = salt ? PGP_SALT_SIZE : 0; + + rnp::secure_vector<uint8_t> data(salt_len + pswd_len); + if (salt_len) { + memcpy(data.data(), salt, PGP_SALT_SIZE); + } + memcpy(data.data() + salt_len, password, pswd_len); + size_t zeroes = 0; + + while (output_len) { + /* create hash context */ + auto hash = rnp::Hash::create(alg); + /* add leading zeroes */ + for (size_t z = 0; z < zeroes; z++) { + uint8_t zero = 0; + hash->add(&zero, 1); + } + if (!data.empty()) { + /* if iteration is 1 then still hash the whole data chunk */ + size_t left = std::max(data.size(), iterations); + while (left) { + size_t to_hash = std::min(left, data.size()); + hash->add(data.data(), to_hash); + left -= to_hash; + } + } + rnp::secure_vector<uint8_t> dgst(hash_len); + size_t out_cpy = std::min(dgst.size(), output_len); + if (hash->finish(dgst.data()) != dgst.size()) { + RNP_LOG("Unexpected digest size."); + return 1; + } + memcpy(out, dgst.data(), out_cpy); + output_len -= out_cpy; + out += out_cpy; + zeroes++; + } + return 0; + } catch (const std::exception &e) { + RNP_LOG("s2k failed: %s", e.what()); + return 1; + } +} diff --git a/src/lib/crypto/sha1cd/sha1.c b/src/lib/crypto/sha1cd/sha1.c new file mode 100644 index 0000000..d90bc41 --- /dev/null +++ b/src/lib/crypto/sha1cd/sha1.c @@ -0,0 +1,2162 @@ +/*** + * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow (danshu@microsoft.com) + * Distributed under the MIT Software License. + * See accompanying file LICENSE.txt or copy at + * https://opensource.org/licenses/MIT + ***/ + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <string.h> +#include <memory.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef __unix__ +#include <sys/types.h> /* make sure macros like _BIG_ENDIAN visible */ +#endif +#endif + +#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_INCLUDE_SHA1_C +#endif + +#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT +#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1 +#endif + +#include "sha1.h" +#include "ubc_check.h" + +#if (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ + defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \ + defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \ + defined(__386) || defined(_M_X64) || defined(_M_AMD64)) +#define SHA1DC_ON_INTEL_LIKE_PROCESSOR +#endif + +/* + Because Little-Endian architectures are most common, + we only set SHA1DC_BIGENDIAN if one of these conditions is met. + Note that all MSFT platforms are little endian, + so none of these will be defined under the MSC compiler. + If you are compiling on a big endian platform and your compiler does not define one of + these, you will have to add whatever macros your tool chain defines to indicate + Big-Endianness. + */ + +#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) +/* + * Should detect Big Endian under GCC since at least 4.6.0 (gcc svn + * rev #165881). See + * https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html + * + * This also works under clang since 3.2, it copied the GCC-ism. See + * clang.git's 3b198a97d2 ("Preprocessor: add __BYTE_ORDER__ + * predefined macro", 2012-07-27) + */ +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike */ +#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) +/* + * Should detect Big Endian under glibc.git since 14245eb70e ("entered + * into RCS", 1992-11-25). Defined in <endian.h> which will have been + * brought in by standard headers. See glibc.git and + * https://sourceforge.net/p/predef/wiki/Endianness/ + */ +#if __BYTE_ORDER == __BIG_ENDIAN +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike or glibc */ +#elif defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) +/* + * *BSD and newlib (embedded linux, cygwin, etc). + * the defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) part prevents + * this condition from matching with Solaris/sparc. + * (Solaris defines only one endian macro) + */ +#if _BYTE_ORDER == _BIG_ENDIAN +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike or glibc or *BSD or newlib */ +#elif (defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(__sparc)) +/* + * Should define Big Endian for a whitelist of known processors. See + * https://sourceforge.net/p/predef/wiki/Endianness/ and + * http://www.oracle.com/technetwork/server-storage/solaris/portingtosolaris-138514.html + */ +#define SHA1DC_BIGENDIAN + +/* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> */ +#elif (defined(_AIX) || defined(__hpux)) + +/* + * Defines Big Endian on a whitelist of OSs that are known to be Big + * Endian-only. See + * https://public-inbox.org/git/93056823-2740-d072-1ebd-46b440b33d7e@felt.demon.nl/ + */ +#define SHA1DC_BIGENDIAN + +/* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> or <os whitelist> */ +#elif defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) +/* + * As a last resort before we do anything else we're not 100% sure + * about below, we blacklist specific processors here. We could add + * more, see e.g. https://wiki.debian.org/ArchitectureSpecificsMemo + */ +#else /* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> or <os \ + whitelist> or <processor blacklist> */ + +/* We do nothing more here for now */ +/*#error "Uncomment this to see if you fall through all the detection"*/ + +#endif /* Big Endian detection */ + +#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN)) +#undef SHA1DC_BIGENDIAN +#endif +#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN)) +#define SHA1DC_BIGENDIAN +#endif +/*ENDIANNESS SELECTION*/ + +#ifndef SHA1DC_FORCE_ALIGNED_ACCESS +#if defined(SHA1DC_FORCE_UNALIGNED_ACCESS) || defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) +#define SHA1DC_ALLOW_UNALIGNED_ACCESS +#endif /*UNALIGNED ACCESS DETECTION*/ +#endif /*FORCE ALIGNED ACCESS*/ + +#define rotate_right(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) +#define rotate_left(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +#define sha1_bswap32(x) \ + { \ + x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0xFF00FF); \ + x = (x << 16) | (x >> 16); \ + } + +#define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1)) + +#ifdef SHA1DC_BIGENDIAN +#define sha1_load(m, t, temp) \ + { \ + temp = m[t]; \ + } +#else +#define sha1_load(m, t, temp) \ + { \ + temp = m[t]; \ + sha1_bswap32(temp); \ + } +#endif + +#define sha1_store(W, t, x) *(volatile uint32_t *) &W[t] = x + +#define sha1_f1(b, c, d) ((d) ^ ((b) & ((c) ^ (d)))) +#define sha1_f2(b, c, d) ((b) ^ (c) ^ (d)) +#define sha1_f3(b, c, d) (((b) & (c)) + ((d) & ((b) ^ (c)))) +#define sha1_f4(b, c, d) ((b) ^ (c) ^ (d)) + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \ + { \ + e += rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999 + m[t]; \ + b = rotate_left(b, 30); \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \ + { \ + e += rotate_left(a, 5) + sha1_f2(b, c, d) + 0x6ED9EBA1 + m[t]; \ + b = rotate_left(b, 30); \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \ + { \ + e += rotate_left(a, 5) + sha1_f3(b, c, d) + 0x8F1BBCDC + m[t]; \ + b = rotate_left(b, 30); \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \ + { \ + e += rotate_left(a, 5) + sha1_f4(b, c, d) + 0xCA62C1D6 + m[t]; \ + b = rotate_left(b, 30); \ + } + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \ + { \ + b = rotate_right(b, 30); \ + e -= rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999 + m[t]; \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \ + { \ + b = rotate_right(b, 30); \ + e -= rotate_left(a, 5) + sha1_f2(b, c, d) + 0x6ED9EBA1 + m[t]; \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \ + { \ + b = rotate_right(b, 30); \ + e -= rotate_left(a, 5) + sha1_f3(b, c, d) + 0x8F1BBCDC + m[t]; \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \ + { \ + b = rotate_right(b, 30); \ + e -= rotate_left(a, 5) + sha1_f4(b, c, d) + 0xCA62C1D6 + m[t]; \ + } + +#define SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, t, temp) \ + { \ + sha1_load(m, t, temp); \ + sha1_store(W, t, temp); \ + e += temp + rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999; \ + b = rotate_left(b, 30); \ + } + +#define SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(a, b, c, d, e, W, t, temp) \ + { \ + temp = sha1_mix(W, t); \ + sha1_store(W, t, temp); \ + e += temp + rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999; \ + b = rotate_left(b, 30); \ + } + +#define SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, t, temp) \ + { \ + temp = sha1_mix(W, t); \ + sha1_store(W, t, temp); \ + e += temp + rotate_left(a, 5) + sha1_f2(b, c, d) + 0x6ED9EBA1; \ + b = rotate_left(b, 30); \ + } + +#define SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, t, temp) \ + { \ + temp = sha1_mix(W, t); \ + sha1_store(W, t, temp); \ + e += temp + rotate_left(a, 5) + sha1_f3(b, c, d) + 0x8F1BBCDC; \ + b = rotate_left(b, 30); \ + } + +#define SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, t, temp) \ + { \ + temp = sha1_mix(W, t); \ + sha1_store(W, t, temp); \ + e += temp + rotate_left(a, 5) + sha1_f4(b, c, d) + 0xCA62C1D6; \ + b = rotate_left(b, 30); \ + } + +#define SHA1_STORE_STATE(i) \ + states[i][0] = a; \ + states[i][1] = b; \ + states[i][2] = c; \ + states[i][3] = d; \ + states[i][4] = e; + +#ifdef BUILDNOCOLLDETECTSHA1COMPRESSION +void +sha1_compression(uint32_t ihv[5], const uint32_t m[16]) +{ + uint32_t W[80]; + uint32_t a, b, c, d, e; + unsigned i; + + memcpy(W, m, 16 * 4); + for (i = 16; i < 80; ++i) + W[i] = sha1_mix(W, i); + + a = ihv[0]; + b = ihv[1]; + c = ihv[2]; + d = ihv[3]; + e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; + ihv[1] += b; + ihv[2] += c; + ihv[3] += d; + ihv[4] += e; +} +#endif /*BUILDNOCOLLDETECTSHA1COMPRESSION*/ + +static void +sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; + ihv[1] += b; + ihv[2] += c; + ihv[3] += d; + ihv[4] += e; +} + +void +sha1_compression_states(uint32_t ihv[5], + const uint32_t m[16], + uint32_t W[80], + uint32_t states[80][5]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + uint32_t temp; + +#ifdef DOSTORESTATE00 + SHA1_STORE_STATE(0) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 0, temp); + +#ifdef DOSTORESTATE01 + SHA1_STORE_STATE(1) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 1, temp); + +#ifdef DOSTORESTATE02 + SHA1_STORE_STATE(2) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 2, temp); + +#ifdef DOSTORESTATE03 + SHA1_STORE_STATE(3) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 3, temp); + +#ifdef DOSTORESTATE04 + SHA1_STORE_STATE(4) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 4, temp); + +#ifdef DOSTORESTATE05 + SHA1_STORE_STATE(5) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 5, temp); + +#ifdef DOSTORESTATE06 + SHA1_STORE_STATE(6) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 6, temp); + +#ifdef DOSTORESTATE07 + SHA1_STORE_STATE(7) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 7, temp); + +#ifdef DOSTORESTATE08 + SHA1_STORE_STATE(8) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 8, temp); + +#ifdef DOSTORESTATE09 + SHA1_STORE_STATE(9) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 9, temp); + +#ifdef DOSTORESTATE10 + SHA1_STORE_STATE(10) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 10, temp); + +#ifdef DOSTORESTATE11 + SHA1_STORE_STATE(11) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 11, temp); + +#ifdef DOSTORESTATE12 + SHA1_STORE_STATE(12) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 12, temp); + +#ifdef DOSTORESTATE13 + SHA1_STORE_STATE(13) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 13, temp); + +#ifdef DOSTORESTATE14 + SHA1_STORE_STATE(14) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 14, temp); + +#ifdef DOSTORESTATE15 + SHA1_STORE_STATE(15) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 15, temp); + +#ifdef DOSTORESTATE16 + SHA1_STORE_STATE(16) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(e, a, b, c, d, W, 16, temp); + +#ifdef DOSTORESTATE17 + SHA1_STORE_STATE(17) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(d, e, a, b, c, W, 17, temp); + +#ifdef DOSTORESTATE18 + SHA1_STORE_STATE(18) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(c, d, e, a, b, W, 18, temp); + +#ifdef DOSTORESTATE19 + SHA1_STORE_STATE(19) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(b, c, d, e, a, W, 19, temp); + +#ifdef DOSTORESTATE20 + SHA1_STORE_STATE(20) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 20, temp); + +#ifdef DOSTORESTATE21 + SHA1_STORE_STATE(21) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 21, temp); + +#ifdef DOSTORESTATE22 + SHA1_STORE_STATE(22) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 22, temp); + +#ifdef DOSTORESTATE23 + SHA1_STORE_STATE(23) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 23, temp); + +#ifdef DOSTORESTATE24 + SHA1_STORE_STATE(24) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 24, temp); + +#ifdef DOSTORESTATE25 + SHA1_STORE_STATE(25) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 25, temp); + +#ifdef DOSTORESTATE26 + SHA1_STORE_STATE(26) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 26, temp); + +#ifdef DOSTORESTATE27 + SHA1_STORE_STATE(27) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 27, temp); + +#ifdef DOSTORESTATE28 + SHA1_STORE_STATE(28) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 28, temp); + +#ifdef DOSTORESTATE29 + SHA1_STORE_STATE(29) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 29, temp); + +#ifdef DOSTORESTATE30 + SHA1_STORE_STATE(30) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 30, temp); + +#ifdef DOSTORESTATE31 + SHA1_STORE_STATE(31) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 31, temp); + +#ifdef DOSTORESTATE32 + SHA1_STORE_STATE(32) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 32, temp); + +#ifdef DOSTORESTATE33 + SHA1_STORE_STATE(33) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 33, temp); + +#ifdef DOSTORESTATE34 + SHA1_STORE_STATE(34) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 34, temp); + +#ifdef DOSTORESTATE35 + SHA1_STORE_STATE(35) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 35, temp); + +#ifdef DOSTORESTATE36 + SHA1_STORE_STATE(36) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 36, temp); + +#ifdef DOSTORESTATE37 + SHA1_STORE_STATE(37) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 37, temp); + +#ifdef DOSTORESTATE38 + SHA1_STORE_STATE(38) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 38, temp); + +#ifdef DOSTORESTATE39 + SHA1_STORE_STATE(39) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 39, temp); + +#ifdef DOSTORESTATE40 + SHA1_STORE_STATE(40) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 40, temp); + +#ifdef DOSTORESTATE41 + SHA1_STORE_STATE(41) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 41, temp); + +#ifdef DOSTORESTATE42 + SHA1_STORE_STATE(42) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 42, temp); + +#ifdef DOSTORESTATE43 + SHA1_STORE_STATE(43) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 43, temp); + +#ifdef DOSTORESTATE44 + SHA1_STORE_STATE(44) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 44, temp); + +#ifdef DOSTORESTATE45 + SHA1_STORE_STATE(45) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 45, temp); + +#ifdef DOSTORESTATE46 + SHA1_STORE_STATE(46) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 46, temp); + +#ifdef DOSTORESTATE47 + SHA1_STORE_STATE(47) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 47, temp); + +#ifdef DOSTORESTATE48 + SHA1_STORE_STATE(48) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 48, temp); + +#ifdef DOSTORESTATE49 + SHA1_STORE_STATE(49) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 49, temp); + +#ifdef DOSTORESTATE50 + SHA1_STORE_STATE(50) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 50, temp); + +#ifdef DOSTORESTATE51 + SHA1_STORE_STATE(51) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 51, temp); + +#ifdef DOSTORESTATE52 + SHA1_STORE_STATE(52) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 52, temp); + +#ifdef DOSTORESTATE53 + SHA1_STORE_STATE(53) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 53, temp); + +#ifdef DOSTORESTATE54 + SHA1_STORE_STATE(54) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 54, temp); + +#ifdef DOSTORESTATE55 + SHA1_STORE_STATE(55) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 55, temp); + +#ifdef DOSTORESTATE56 + SHA1_STORE_STATE(56) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 56, temp); + +#ifdef DOSTORESTATE57 + SHA1_STORE_STATE(57) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 57, temp); + +#ifdef DOSTORESTATE58 + SHA1_STORE_STATE(58) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 58, temp); + +#ifdef DOSTORESTATE59 + SHA1_STORE_STATE(59) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 59, temp); + +#ifdef DOSTORESTATE60 + SHA1_STORE_STATE(60) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 60, temp); + +#ifdef DOSTORESTATE61 + SHA1_STORE_STATE(61) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 61, temp); + +#ifdef DOSTORESTATE62 + SHA1_STORE_STATE(62) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 62, temp); + +#ifdef DOSTORESTATE63 + SHA1_STORE_STATE(63) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 63, temp); + +#ifdef DOSTORESTATE64 + SHA1_STORE_STATE(64) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 64, temp); + +#ifdef DOSTORESTATE65 + SHA1_STORE_STATE(65) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 65, temp); + +#ifdef DOSTORESTATE66 + SHA1_STORE_STATE(66) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 66, temp); + +#ifdef DOSTORESTATE67 + SHA1_STORE_STATE(67) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 67, temp); + +#ifdef DOSTORESTATE68 + SHA1_STORE_STATE(68) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 68, temp); + +#ifdef DOSTORESTATE69 + SHA1_STORE_STATE(69) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 69, temp); + +#ifdef DOSTORESTATE70 + SHA1_STORE_STATE(70) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 70, temp); + +#ifdef DOSTORESTATE71 + SHA1_STORE_STATE(71) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 71, temp); + +#ifdef DOSTORESTATE72 + SHA1_STORE_STATE(72) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 72, temp); + +#ifdef DOSTORESTATE73 + SHA1_STORE_STATE(73) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 73, temp); + +#ifdef DOSTORESTATE74 + SHA1_STORE_STATE(74) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 74, temp); + +#ifdef DOSTORESTATE75 + SHA1_STORE_STATE(75) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 75, temp); + +#ifdef DOSTORESTATE76 + SHA1_STORE_STATE(76) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 76, temp); + +#ifdef DOSTORESTATE77 + SHA1_STORE_STATE(77) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 77, temp); + +#ifdef DOSTORESTATE78 + SHA1_STORE_STATE(78) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 78, temp); + +#ifdef DOSTORESTATE79 + SHA1_STORE_STATE(79) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 79, temp); + + ihv[0] += a; + ihv[1] += b; + ihv[2] += c; + ihv[3] += d; + ihv[4] += e; +} + +#define SHA1_RECOMPRESS(t) \ + static void sha1recompress_fast_##t( \ + uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \ + { \ + uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \ + if (t > 79) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \ + if (t > 78) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \ + if (t > 77) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \ + if (t > 76) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \ + if (t > 75) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \ + if (t > 74) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \ + if (t > 73) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \ + if (t > 72) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \ + if (t > 71) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \ + if (t > 70) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \ + if (t > 69) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \ + if (t > 68) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \ + if (t > 67) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \ + if (t > 66) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \ + if (t > 65) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \ + if (t > 64) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \ + if (t > 63) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \ + if (t > 62) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \ + if (t > 61) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \ + if (t > 60) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \ + if (t > 59) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \ + if (t > 58) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \ + if (t > 57) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \ + if (t > 56) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \ + if (t > 55) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \ + if (t > 54) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \ + if (t > 53) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \ + if (t > 52) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \ + if (t > 51) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \ + if (t > 50) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \ + if (t > 49) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \ + if (t > 48) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \ + if (t > 47) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \ + if (t > 46) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \ + if (t > 45) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \ + if (t > 44) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \ + if (t > 43) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \ + if (t > 42) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \ + if (t > 41) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \ + if (t > 40) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \ + if (t > 39) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \ + if (t > 38) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \ + if (t > 37) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \ + if (t > 36) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \ + if (t > 35) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \ + if (t > 34) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \ + if (t > 33) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \ + if (t > 32) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \ + if (t > 31) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \ + if (t > 30) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \ + if (t > 29) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \ + if (t > 28) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \ + if (t > 27) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \ + if (t > 26) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \ + if (t > 25) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \ + if (t > 24) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \ + if (t > 23) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \ + if (t > 22) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \ + if (t > 21) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \ + if (t > 20) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \ + if (t > 19) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \ + if (t > 18) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \ + if (t > 17) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \ + if (t > 16) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \ + if (t > 15) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \ + if (t > 14) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \ + if (t > 13) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \ + if (t > 12) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \ + if (t > 11) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \ + if (t > 10) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \ + if (t > 9) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \ + if (t > 8) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \ + if (t > 7) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \ + if (t > 6) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \ + if (t > 5) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \ + if (t > 4) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \ + if (t > 3) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \ + if (t > 2) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \ + if (t > 1) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \ + if (t > 0) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \ + ihvin[0] = a; \ + ihvin[1] = b; \ + ihvin[2] = c; \ + ihvin[3] = d; \ + ihvin[4] = e; \ + a = state[0]; \ + b = state[1]; \ + c = state[2]; \ + d = state[3]; \ + e = state[4]; \ + if (t <= 0) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \ + if (t <= 1) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \ + if (t <= 2) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \ + if (t <= 3) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \ + if (t <= 4) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \ + if (t <= 5) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \ + if (t <= 6) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \ + if (t <= 7) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \ + if (t <= 8) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \ + if (t <= 9) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \ + if (t <= 10) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \ + if (t <= 11) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \ + if (t <= 12) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \ + if (t <= 13) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \ + if (t <= 14) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \ + if (t <= 15) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \ + if (t <= 16) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \ + if (t <= 17) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \ + if (t <= 18) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \ + if (t <= 19) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \ + if (t <= 20) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \ + if (t <= 21) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \ + if (t <= 22) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \ + if (t <= 23) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \ + if (t <= 24) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \ + if (t <= 25) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \ + if (t <= 26) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \ + if (t <= 27) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \ + if (t <= 28) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \ + if (t <= 29) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \ + if (t <= 30) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \ + if (t <= 31) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \ + if (t <= 32) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \ + if (t <= 33) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \ + if (t <= 34) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \ + if (t <= 35) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \ + if (t <= 36) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \ + if (t <= 37) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \ + if (t <= 38) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \ + if (t <= 39) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \ + if (t <= 40) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \ + if (t <= 41) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \ + if (t <= 42) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \ + if (t <= 43) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \ + if (t <= 44) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \ + if (t <= 45) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \ + if (t <= 46) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \ + if (t <= 47) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \ + if (t <= 48) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \ + if (t <= 49) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \ + if (t <= 50) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \ + if (t <= 51) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \ + if (t <= 52) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \ + if (t <= 53) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \ + if (t <= 54) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \ + if (t <= 55) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \ + if (t <= 56) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \ + if (t <= 57) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \ + if (t <= 58) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \ + if (t <= 59) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \ + if (t <= 60) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \ + if (t <= 61) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \ + if (t <= 62) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \ + if (t <= 63) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \ + if (t <= 64) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \ + if (t <= 65) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \ + if (t <= 66) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \ + if (t <= 67) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \ + if (t <= 68) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \ + if (t <= 69) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \ + if (t <= 70) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \ + if (t <= 71) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \ + if (t <= 72) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \ + if (t <= 73) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \ + if (t <= 74) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \ + if (t <= 75) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \ + if (t <= 76) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \ + if (t <= 77) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \ + if (t <= 78) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \ + if (t <= 79) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \ + ihvout[0] = ihvin[0] + a; \ + ihvout[1] = ihvin[1] + b; \ + ihvout[2] = ihvin[2] + c; \ + ihvout[3] = ihvin[3] + d; \ + ihvout[4] = ihvin[4] + e; \ + } + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4127) /* Compiler complains about the checks in the above macro \ + being constant. */ +#endif + +#ifdef DOSTORESTATE0 +SHA1_RECOMPRESS(0) +#endif + +#ifdef DOSTORESTATE1 +SHA1_RECOMPRESS(1) +#endif + +#ifdef DOSTORESTATE2 +SHA1_RECOMPRESS(2) +#endif + +#ifdef DOSTORESTATE3 +SHA1_RECOMPRESS(3) +#endif + +#ifdef DOSTORESTATE4 +SHA1_RECOMPRESS(4) +#endif + +#ifdef DOSTORESTATE5 +SHA1_RECOMPRESS(5) +#endif + +#ifdef DOSTORESTATE6 +SHA1_RECOMPRESS(6) +#endif + +#ifdef DOSTORESTATE7 +SHA1_RECOMPRESS(7) +#endif + +#ifdef DOSTORESTATE8 +SHA1_RECOMPRESS(8) +#endif + +#ifdef DOSTORESTATE9 +SHA1_RECOMPRESS(9) +#endif + +#ifdef DOSTORESTATE10 +SHA1_RECOMPRESS(10) +#endif + +#ifdef DOSTORESTATE11 +SHA1_RECOMPRESS(11) +#endif + +#ifdef DOSTORESTATE12 +SHA1_RECOMPRESS(12) +#endif + +#ifdef DOSTORESTATE13 +SHA1_RECOMPRESS(13) +#endif + +#ifdef DOSTORESTATE14 +SHA1_RECOMPRESS(14) +#endif + +#ifdef DOSTORESTATE15 +SHA1_RECOMPRESS(15) +#endif + +#ifdef DOSTORESTATE16 +SHA1_RECOMPRESS(16) +#endif + +#ifdef DOSTORESTATE17 +SHA1_RECOMPRESS(17) +#endif + +#ifdef DOSTORESTATE18 +SHA1_RECOMPRESS(18) +#endif + +#ifdef DOSTORESTATE19 +SHA1_RECOMPRESS(19) +#endif + +#ifdef DOSTORESTATE20 +SHA1_RECOMPRESS(20) +#endif + +#ifdef DOSTORESTATE21 +SHA1_RECOMPRESS(21) +#endif + +#ifdef DOSTORESTATE22 +SHA1_RECOMPRESS(22) +#endif + +#ifdef DOSTORESTATE23 +SHA1_RECOMPRESS(23) +#endif + +#ifdef DOSTORESTATE24 +SHA1_RECOMPRESS(24) +#endif + +#ifdef DOSTORESTATE25 +SHA1_RECOMPRESS(25) +#endif + +#ifdef DOSTORESTATE26 +SHA1_RECOMPRESS(26) +#endif + +#ifdef DOSTORESTATE27 +SHA1_RECOMPRESS(27) +#endif + +#ifdef DOSTORESTATE28 +SHA1_RECOMPRESS(28) +#endif + +#ifdef DOSTORESTATE29 +SHA1_RECOMPRESS(29) +#endif + +#ifdef DOSTORESTATE30 +SHA1_RECOMPRESS(30) +#endif + +#ifdef DOSTORESTATE31 +SHA1_RECOMPRESS(31) +#endif + +#ifdef DOSTORESTATE32 +SHA1_RECOMPRESS(32) +#endif + +#ifdef DOSTORESTATE33 +SHA1_RECOMPRESS(33) +#endif + +#ifdef DOSTORESTATE34 +SHA1_RECOMPRESS(34) +#endif + +#ifdef DOSTORESTATE35 +SHA1_RECOMPRESS(35) +#endif + +#ifdef DOSTORESTATE36 +SHA1_RECOMPRESS(36) +#endif + +#ifdef DOSTORESTATE37 +SHA1_RECOMPRESS(37) +#endif + +#ifdef DOSTORESTATE38 +SHA1_RECOMPRESS(38) +#endif + +#ifdef DOSTORESTATE39 +SHA1_RECOMPRESS(39) +#endif + +#ifdef DOSTORESTATE40 +SHA1_RECOMPRESS(40) +#endif + +#ifdef DOSTORESTATE41 +SHA1_RECOMPRESS(41) +#endif + +#ifdef DOSTORESTATE42 +SHA1_RECOMPRESS(42) +#endif + +#ifdef DOSTORESTATE43 +SHA1_RECOMPRESS(43) +#endif + +#ifdef DOSTORESTATE44 +SHA1_RECOMPRESS(44) +#endif + +#ifdef DOSTORESTATE45 +SHA1_RECOMPRESS(45) +#endif + +#ifdef DOSTORESTATE46 +SHA1_RECOMPRESS(46) +#endif + +#ifdef DOSTORESTATE47 +SHA1_RECOMPRESS(47) +#endif + +#ifdef DOSTORESTATE48 +SHA1_RECOMPRESS(48) +#endif + +#ifdef DOSTORESTATE49 +SHA1_RECOMPRESS(49) +#endif + +#ifdef DOSTORESTATE50 +SHA1_RECOMPRESS(50) +#endif + +#ifdef DOSTORESTATE51 +SHA1_RECOMPRESS(51) +#endif + +#ifdef DOSTORESTATE52 +SHA1_RECOMPRESS(52) +#endif + +#ifdef DOSTORESTATE53 +SHA1_RECOMPRESS(53) +#endif + +#ifdef DOSTORESTATE54 +SHA1_RECOMPRESS(54) +#endif + +#ifdef DOSTORESTATE55 +SHA1_RECOMPRESS(55) +#endif + +#ifdef DOSTORESTATE56 +SHA1_RECOMPRESS(56) +#endif + +#ifdef DOSTORESTATE57 +SHA1_RECOMPRESS(57) +#endif + +#ifdef DOSTORESTATE58 +SHA1_RECOMPRESS(58) +#endif + +#ifdef DOSTORESTATE59 +SHA1_RECOMPRESS(59) +#endif + +#ifdef DOSTORESTATE60 +SHA1_RECOMPRESS(60) +#endif + +#ifdef DOSTORESTATE61 +SHA1_RECOMPRESS(61) +#endif + +#ifdef DOSTORESTATE62 +SHA1_RECOMPRESS(62) +#endif + +#ifdef DOSTORESTATE63 +SHA1_RECOMPRESS(63) +#endif + +#ifdef DOSTORESTATE64 +SHA1_RECOMPRESS(64) +#endif + +#ifdef DOSTORESTATE65 +SHA1_RECOMPRESS(65) +#endif + +#ifdef DOSTORESTATE66 +SHA1_RECOMPRESS(66) +#endif + +#ifdef DOSTORESTATE67 +SHA1_RECOMPRESS(67) +#endif + +#ifdef DOSTORESTATE68 +SHA1_RECOMPRESS(68) +#endif + +#ifdef DOSTORESTATE69 +SHA1_RECOMPRESS(69) +#endif + +#ifdef DOSTORESTATE70 +SHA1_RECOMPRESS(70) +#endif + +#ifdef DOSTORESTATE71 +SHA1_RECOMPRESS(71) +#endif + +#ifdef DOSTORESTATE72 +SHA1_RECOMPRESS(72) +#endif + +#ifdef DOSTORESTATE73 +SHA1_RECOMPRESS(73) +#endif + +#ifdef DOSTORESTATE74 +SHA1_RECOMPRESS(74) +#endif + +#ifdef DOSTORESTATE75 +SHA1_RECOMPRESS(75) +#endif + +#ifdef DOSTORESTATE76 +SHA1_RECOMPRESS(76) +#endif + +#ifdef DOSTORESTATE77 +SHA1_RECOMPRESS(77) +#endif + +#ifdef DOSTORESTATE78 +SHA1_RECOMPRESS(78) +#endif + +#ifdef DOSTORESTATE79 +SHA1_RECOMPRESS(79) +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +static void +sha1_recompression_step(uint32_t step, + uint32_t ihvin[5], + uint32_t ihvout[5], + const uint32_t me2[80], + const uint32_t state[5]) +{ + switch (step) { +#ifdef DOSTORESTATE0 + case 0: + sha1recompress_fast_0(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE1 + case 1: + sha1recompress_fast_1(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE2 + case 2: + sha1recompress_fast_2(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE3 + case 3: + sha1recompress_fast_3(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE4 + case 4: + sha1recompress_fast_4(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE5 + case 5: + sha1recompress_fast_5(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE6 + case 6: + sha1recompress_fast_6(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE7 + case 7: + sha1recompress_fast_7(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE8 + case 8: + sha1recompress_fast_8(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE9 + case 9: + sha1recompress_fast_9(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE10 + case 10: + sha1recompress_fast_10(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE11 + case 11: + sha1recompress_fast_11(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE12 + case 12: + sha1recompress_fast_12(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE13 + case 13: + sha1recompress_fast_13(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE14 + case 14: + sha1recompress_fast_14(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE15 + case 15: + sha1recompress_fast_15(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE16 + case 16: + sha1recompress_fast_16(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE17 + case 17: + sha1recompress_fast_17(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE18 + case 18: + sha1recompress_fast_18(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE19 + case 19: + sha1recompress_fast_19(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE20 + case 20: + sha1recompress_fast_20(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE21 + case 21: + sha1recompress_fast_21(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE22 + case 22: + sha1recompress_fast_22(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE23 + case 23: + sha1recompress_fast_23(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE24 + case 24: + sha1recompress_fast_24(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE25 + case 25: + sha1recompress_fast_25(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE26 + case 26: + sha1recompress_fast_26(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE27 + case 27: + sha1recompress_fast_27(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE28 + case 28: + sha1recompress_fast_28(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE29 + case 29: + sha1recompress_fast_29(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE30 + case 30: + sha1recompress_fast_30(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE31 + case 31: + sha1recompress_fast_31(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE32 + case 32: + sha1recompress_fast_32(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE33 + case 33: + sha1recompress_fast_33(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE34 + case 34: + sha1recompress_fast_34(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE35 + case 35: + sha1recompress_fast_35(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE36 + case 36: + sha1recompress_fast_36(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE37 + case 37: + sha1recompress_fast_37(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE38 + case 38: + sha1recompress_fast_38(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE39 + case 39: + sha1recompress_fast_39(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE40 + case 40: + sha1recompress_fast_40(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE41 + case 41: + sha1recompress_fast_41(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE42 + case 42: + sha1recompress_fast_42(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE43 + case 43: + sha1recompress_fast_43(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE44 + case 44: + sha1recompress_fast_44(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE45 + case 45: + sha1recompress_fast_45(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE46 + case 46: + sha1recompress_fast_46(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE47 + case 47: + sha1recompress_fast_47(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE48 + case 48: + sha1recompress_fast_48(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE49 + case 49: + sha1recompress_fast_49(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE50 + case 50: + sha1recompress_fast_50(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE51 + case 51: + sha1recompress_fast_51(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE52 + case 52: + sha1recompress_fast_52(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE53 + case 53: + sha1recompress_fast_53(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE54 + case 54: + sha1recompress_fast_54(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE55 + case 55: + sha1recompress_fast_55(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE56 + case 56: + sha1recompress_fast_56(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE57 + case 57: + sha1recompress_fast_57(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE58 + case 58: + sha1recompress_fast_58(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE59 + case 59: + sha1recompress_fast_59(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE60 + case 60: + sha1recompress_fast_60(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE61 + case 61: + sha1recompress_fast_61(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE62 + case 62: + sha1recompress_fast_62(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE63 + case 63: + sha1recompress_fast_63(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE64 + case 64: + sha1recompress_fast_64(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE65 + case 65: + sha1recompress_fast_65(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE66 + case 66: + sha1recompress_fast_66(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE67 + case 67: + sha1recompress_fast_67(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE68 + case 68: + sha1recompress_fast_68(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE69 + case 69: + sha1recompress_fast_69(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE70 + case 70: + sha1recompress_fast_70(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE71 + case 71: + sha1recompress_fast_71(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE72 + case 72: + sha1recompress_fast_72(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE73 + case 73: + sha1recompress_fast_73(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE74 + case 74: + sha1recompress_fast_74(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE75 + case 75: + sha1recompress_fast_75(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE76 + case 76: + sha1recompress_fast_76(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE77 + case 77: + sha1recompress_fast_77(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE78 + case 78: + sha1recompress_fast_78(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE79 + case 79: + sha1recompress_fast_79(ihvin, ihvout, me2, state); + break; +#endif + default: + abort(); + } +} + +static void +sha1_process(SHA1_CTX *ctx, const uint32_t block[16]) +{ + unsigned i, j; + uint32_t ubc_dv_mask[DVMASKSIZE] = {0xFFFFFFFF}; + uint32_t ihvtmp[5]; + + ctx->ihv1[0] = ctx->ihv[0]; + ctx->ihv1[1] = ctx->ihv[1]; + ctx->ihv1[2] = ctx->ihv[2]; + ctx->ihv1[3] = ctx->ihv[3]; + ctx->ihv1[4] = ctx->ihv[4]; + + sha1_compression_states(ctx->ihv, block, ctx->m1, ctx->states); + + if (ctx->detect_coll) { + if (ctx->ubc_check) { + ubc_check(ctx->m1, ubc_dv_mask); + } + + if (ubc_dv_mask[0] != 0) { + for (i = 0; sha1_dvs[i].dvType != 0; ++i) { + if (ubc_dv_mask[0] & ((uint32_t)(1) << sha1_dvs[i].maskb)) { + for (j = 0; j < 80; ++j) + ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j]; + + sha1_recompression_step(sha1_dvs[i].testt, + ctx->ihv2, + ihvtmp, + ctx->m2, + ctx->states[sha1_dvs[i].testt]); + + /* to verify SHA-1 collision detection code with collisions for + * reduced-step SHA-1 */ + if ((0 == ((ihvtmp[0] ^ ctx->ihv[0]) | (ihvtmp[1] ^ ctx->ihv[1]) | + (ihvtmp[2] ^ ctx->ihv[2]) | (ihvtmp[3] ^ ctx->ihv[3]) | + (ihvtmp[4] ^ ctx->ihv[4]))) || + (ctx->reduced_round_coll && + 0 == ((ctx->ihv1[0] ^ ctx->ihv2[0]) | (ctx->ihv1[1] ^ ctx->ihv2[1]) | + (ctx->ihv1[2] ^ ctx->ihv2[2]) | (ctx->ihv1[3] ^ ctx->ihv2[3]) | + (ctx->ihv1[4] ^ ctx->ihv2[4])))) { + ctx->found_collision = 1; + + if (ctx->safe_hash) { + sha1_compression_W(ctx->ihv, ctx->m1); + sha1_compression_W(ctx->ihv, ctx->m1); + } + + break; + } + } + } + } + } +} + +void +SHA1DCInit(SHA1_CTX *ctx) +{ + ctx->total = 0; + ctx->ihv[0] = 0x67452301; + ctx->ihv[1] = 0xEFCDAB89; + ctx->ihv[2] = 0x98BADCFE; + ctx->ihv[3] = 0x10325476; + ctx->ihv[4] = 0xC3D2E1F0; + ctx->found_collision = 0; + ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT; + ctx->ubc_check = 1; + ctx->detect_coll = 1; + ctx->reduced_round_coll = 0; + ctx->callback = NULL; +} + +void +SHA1DCSetSafeHash(SHA1_CTX *ctx, int safehash) +{ + if (safehash) + ctx->safe_hash = 1; + else + ctx->safe_hash = 0; +} + +void +SHA1DCSetUseUBC(SHA1_CTX *ctx, int ubc_check) +{ + if (ubc_check) + ctx->ubc_check = 1; + else + ctx->ubc_check = 0; +} + +void +SHA1DCSetUseDetectColl(SHA1_CTX *ctx, int detect_coll) +{ + if (detect_coll) + ctx->detect_coll = 1; + else + ctx->detect_coll = 0; +} + +void +SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *ctx, int reduced_round_coll) +{ + if (reduced_round_coll) + ctx->reduced_round_coll = 1; + else + ctx->reduced_round_coll = 0; +} + +void +SHA1DCSetCallback(SHA1_CTX *ctx, collision_block_callback callback) +{ + ctx->callback = callback; +} + +void +SHA1DCUpdate(SHA1_CTX *ctx, const char *buf, size_t len) +{ + unsigned left, fill; + + if (len == 0) + return; + + left = ctx->total & 63; + fill = 64 - left; + + if (left && len >= fill) { + ctx->total += fill; + memcpy(ctx->buffer + left, buf, fill); + sha1_process(ctx, (uint32_t *) (ctx->buffer)); + buf += fill; + len -= fill; + left = 0; + } + while (len >= 64) { + ctx->total += 64; + +#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) + sha1_process(ctx, (uint32_t *) (buf)); +#else + memcpy(ctx->buffer, buf, 64); + sha1_process(ctx, (uint32_t *) (ctx->buffer)); +#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */ + buf += 64; + len -= 64; + } + if (len > 0) { + ctx->total += len; + memcpy(ctx->buffer + left, buf, len); + } +} + +static const unsigned char sha1_padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +int +SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) +{ + uint32_t last = ctx->total & 63; + uint32_t padn = (last < 56) ? (56 - last) : (120 - last); + uint64_t total; + SHA1DCUpdate(ctx, (const char *) (sha1_padding), padn); + + total = ctx->total - padn; + total <<= 3; + ctx->buffer[56] = (unsigned char) (total >> 56); + ctx->buffer[57] = (unsigned char) (total >> 48); + ctx->buffer[58] = (unsigned char) (total >> 40); + ctx->buffer[59] = (unsigned char) (total >> 32); + ctx->buffer[60] = (unsigned char) (total >> 24); + ctx->buffer[61] = (unsigned char) (total >> 16); + ctx->buffer[62] = (unsigned char) (total >> 8); + ctx->buffer[63] = (unsigned char) (total); + sha1_process(ctx, (uint32_t *) (ctx->buffer)); + output[0] = (unsigned char) (ctx->ihv[0] >> 24); + output[1] = (unsigned char) (ctx->ihv[0] >> 16); + output[2] = (unsigned char) (ctx->ihv[0] >> 8); + output[3] = (unsigned char) (ctx->ihv[0]); + output[4] = (unsigned char) (ctx->ihv[1] >> 24); + output[5] = (unsigned char) (ctx->ihv[1] >> 16); + output[6] = (unsigned char) (ctx->ihv[1] >> 8); + output[7] = (unsigned char) (ctx->ihv[1]); + output[8] = (unsigned char) (ctx->ihv[2] >> 24); + output[9] = (unsigned char) (ctx->ihv[2] >> 16); + output[10] = (unsigned char) (ctx->ihv[2] >> 8); + output[11] = (unsigned char) (ctx->ihv[2]); + output[12] = (unsigned char) (ctx->ihv[3] >> 24); + output[13] = (unsigned char) (ctx->ihv[3] >> 16); + output[14] = (unsigned char) (ctx->ihv[3] >> 8); + output[15] = (unsigned char) (ctx->ihv[3]); + output[16] = (unsigned char) (ctx->ihv[4] >> 24); + output[17] = (unsigned char) (ctx->ihv[4] >> 16); + output[18] = (unsigned char) (ctx->ihv[4] >> 8); + output[19] = (unsigned char) (ctx->ihv[4]); + return ctx->found_collision; +} + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#endif diff --git a/src/lib/crypto/sha1cd/sha1.h b/src/lib/crypto/sha1cd/sha1.h new file mode 100644 index 0000000..5bc0925 --- /dev/null +++ b/src/lib/crypto/sha1cd/sha1.h @@ -0,0 +1,122 @@ +/*** + * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com> + * Distributed under the MIT Software License. + * See accompanying file LICENSE.txt or copy at + * https://opensource.org/licenses/MIT + ***/ + +#ifndef SHA1DC_SHA1_H +#define SHA1DC_SHA1_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <stdint.h> +#endif + +/* sha-1 compression function that takes an already expanded message, and additionally store + * intermediate states */ +/* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is + * defined in ubc_check.h */ +void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]); + +/* +// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const +uint32_t me2[80], const uint32_t state[5]). +// Where 0 <= T < 80 +// me2 is an expanded message (the expansion of an original message block XOR'ed with a +disturbance vector's message block difference.) +// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression +function while processing the original message block. +// The function will return: +// ihvin: The reconstructed input chaining value. +// ihvout: The reconstructed output chaining value. +*/ +typedef void (*sha1_recompression_type)(uint32_t *, + uint32_t *, + const uint32_t *, + const uint32_t *); + +/* A callback function type that can be set to be called when a collision block has been found: + */ +/* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t + * ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */ +typedef void (*collision_block_callback)( + uint64_t, const uint32_t *, const uint32_t *, const uint32_t *, const uint32_t *); + +/* The SHA-1 context. */ +typedef struct { + uint64_t total; + uint32_t ihv[5]; + unsigned char buffer[64]; + int found_collision; + int safe_hash; + int detect_coll; + int ubc_check; + int reduced_round_coll; + collision_block_callback callback; + + uint32_t ihv1[5]; + uint32_t ihv2[5]; + uint32_t m1[80]; + uint32_t m2[80]; + uint32_t states[80][5]; +} SHA1_CTX; + +/* Initialize SHA-1 context. */ +void SHA1DCInit(SHA1_CTX *); + +/* + Function to enable safe SHA-1 hashing: + Collision attacks are thwarted by hashing a detected near-collision block 3 times. + Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: + The best collision attacks against SHA-1 have complexity about 2^60, + thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be + 2^180. An attacker would be better off using a generic birthday search of complexity 2^80. + + Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no + collision attack was detected, but it will result in a different SHA-1 hash for messages + where a collision attack was detected. This will automatically invalidate SHA-1 based + digital signature forgeries. Enabled by default. +*/ +void SHA1DCSetSafeHash(SHA1_CTX *, int); + +/* + Function to disable or enable the use of Unavoidable Bitconditions (provides a significant + speed up). Enabled by default + */ +void SHA1DCSetUseUBC(SHA1_CTX *, int); + +/* + Function to disable or enable the use of Collision Detection. + Enabled by default. + */ +void SHA1DCSetUseDetectColl(SHA1_CTX *, int); + +/* function to disable or enable the detection of reduced-round SHA-1 collisions */ +/* disabled by default */ +void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *, int); + +/* function to set a callback function, pass NULL to disable */ +/* by default no callback set */ +void SHA1DCSetCallback(SHA1_CTX *, collision_block_callback); + +/* update SHA-1 context with buffer contents */ +void SHA1DCUpdate(SHA1_CTX *, const char *, size_t); + +/* obtain SHA-1 hash from SHA-1 context */ +/* returns: 0 = no collision detected, otherwise = collision found => warn user for active + * attack */ +int SHA1DCFinal(unsigned char[20], SHA1_CTX *); + +#if defined(__cplusplus) +} +#endif + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#endif + +#endif diff --git a/src/lib/crypto/sha1cd/ubc_check.c b/src/lib/crypto/sha1cd/ubc_check.c new file mode 100644 index 0000000..c44c53d --- /dev/null +++ b/src/lib/crypto/sha1cd/ubc_check.c @@ -0,0 +1,908 @@ +/*** + * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com> + * Distributed under the MIT Software License. + * See accompanying file LICENSE.txt or copy at + * https://opensource.org/licenses/MIT + ***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable +bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions +for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +// +// ubc_check is programmatically generated and the unavoidable bitconditions have been +hardcoded +// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c +// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in +the tools section +*/ + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <stdint.h> +#endif +#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#endif +#include "ubc_check.h" + +static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0; +static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1; +static const uint32_t DV_I_45_0_bit = (uint32_t)(1) << 2; +static const uint32_t DV_I_46_0_bit = (uint32_t)(1) << 3; +static const uint32_t DV_I_46_2_bit = (uint32_t)(1) << 4; +static const uint32_t DV_I_47_0_bit = (uint32_t)(1) << 5; +static const uint32_t DV_I_47_2_bit = (uint32_t)(1) << 6; +static const uint32_t DV_I_48_0_bit = (uint32_t)(1) << 7; +static const uint32_t DV_I_48_2_bit = (uint32_t)(1) << 8; +static const uint32_t DV_I_49_0_bit = (uint32_t)(1) << 9; +static const uint32_t DV_I_49_2_bit = (uint32_t)(1) << 10; +static const uint32_t DV_I_50_0_bit = (uint32_t)(1) << 11; +static const uint32_t DV_I_50_2_bit = (uint32_t)(1) << 12; +static const uint32_t DV_I_51_0_bit = (uint32_t)(1) << 13; +static const uint32_t DV_I_51_2_bit = (uint32_t)(1) << 14; +static const uint32_t DV_I_52_0_bit = (uint32_t)(1) << 15; +static const uint32_t DV_II_45_0_bit = (uint32_t)(1) << 16; +static const uint32_t DV_II_46_0_bit = (uint32_t)(1) << 17; +static const uint32_t DV_II_46_2_bit = (uint32_t)(1) << 18; +static const uint32_t DV_II_47_0_bit = (uint32_t)(1) << 19; +static const uint32_t DV_II_48_0_bit = (uint32_t)(1) << 20; +static const uint32_t DV_II_49_0_bit = (uint32_t)(1) << 21; +static const uint32_t DV_II_49_2_bit = (uint32_t)(1) << 22; +static const uint32_t DV_II_50_0_bit = (uint32_t)(1) << 23; +static const uint32_t DV_II_50_2_bit = (uint32_t)(1) << 24; +static const uint32_t DV_II_51_0_bit = (uint32_t)(1) << 25; +static const uint32_t DV_II_51_2_bit = (uint32_t)(1) << 26; +static const uint32_t DV_II_52_0_bit = (uint32_t)(1) << 27; +static const uint32_t DV_II_53_0_bit = (uint32_t)(1) << 28; +static const uint32_t DV_II_54_0_bit = (uint32_t)(1) << 29; +static const uint32_t DV_II_55_0_bit = (uint32_t)(1) << 30; +static const uint32_t DV_II_56_0_bit = (uint32_t)(1) << 31; + +dv_info_t sha1_dvs[] = { + {1, 43, 0, 58, 0, 0, {0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, + 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, + 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010, + 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, + 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, + 0x90000010, 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000, + 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, + 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202, + 0x00000018, 0x00000164, 0x00000408, 0x800000e6, 0x8000004c, 0x00000803, + 0x80000161, 0x80000599}}, + {1, 44, 0, 58, 0, 1, {0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, + 0x98000000, 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, + 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, + 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, + 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, + 0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x00000010, 0xa0000000, + 0x20000000, 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000020, 0x00000001, 0x40000002, 0x40000040, 0x40000002, 0x80000004, + 0x80000080, 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012, + 0x80000202, 0x00000018, 0x00000164, 0x00000408, 0x800000e6, 0x8000004c, + 0x00000803, 0x80000161}}, + {1, 45, 0, 58, 0, 2, {0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, + 0xb8000010, 0x98000000, 0x60000000, 0x00000008, 0xc0000000, 0x90000014, + 0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x08000018, + 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, + 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, + 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x00000010, + 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x20000000, 0x00000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000040, 0x40000002, + 0x80000004, 0x80000080, 0x80000006, 0x00000049, 0x00000103, 0x80000009, + 0x80000012, 0x80000202, 0x00000018, 0x00000164, 0x00000408, 0x800000e6, + 0x8000004c, 0x00000803}}, + {1, 46, 0, 58, 0, 3, {0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, + 0x08000010, 0xb8000010, 0x98000000, 0x60000000, 0x00000008, 0xc0000000, + 0x90000014, 0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000, + 0x08000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, + 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, + 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000, + 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x20000000, 0x00000010, 0xa0000000, + 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000040, + 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x00000049, 0x00000103, + 0x80000009, 0x80000012, 0x80000202, 0x00000018, 0x00000164, 0x00000408, + 0x800000e6, 0x8000004c}}, + {1, 46, 2, 58, 0, 4, {0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, 0x60000043, + 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x00000020, 0x00000003, + 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, 0x20000001, + 0x20000060, 0x80000001, 0x40000042, 0xc0000043, 0x40000022, 0x00000003, + 0x40000042, 0xc0000043, 0xc0000022, 0x00000001, 0x40000002, 0xc0000043, + 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002, 0x00000002, + 0x00000040, 0x80000002, 0x80000000, 0x80000002, 0x80000040, 0x00000000, + 0x80000040, 0x80000000, 0x00000040, 0x80000000, 0x00000040, 0x80000002, + 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, 0x00000101, + 0x00000009, 0x00000012, 0x00000202, 0x0000001a, 0x00000124, 0x0000040c, + 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, 0x00000590, 0x00001020, + 0x0000039a, 0x00000132}}, + {1, 47, 0, 58, 0, 5, {0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, + 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000, 0x00000008, + 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000, 0x20000010, + 0x48000000, 0x08000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008, + 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, + 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, + 0x80000000, 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x20000000, 0x00000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x00000049, + 0x00000103, 0x80000009, 0x80000012, 0x80000202, 0x00000018, 0x00000164, + 0x00000408, 0x800000e6}}, + {1, 47, 2, 58, 0, 6, {0x20000043, 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, + 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x00000020, + 0x00000003, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, + 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043, 0x40000022, + 0x00000003, 0x40000042, 0xc0000043, 0xc0000022, 0x00000001, 0x40000002, + 0xc0000043, 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002, + 0x00000002, 0x00000040, 0x80000002, 0x80000000, 0x80000002, 0x80000040, + 0x00000000, 0x80000040, 0x80000000, 0x00000040, 0x80000000, 0x00000040, + 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, + 0x00000101, 0x00000009, 0x00000012, 0x00000202, 0x0000001a, 0x00000124, + 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, 0x00000590, + 0x00001020, 0x0000039a}}, + {1, 48, 0, 58, 0, 7, {0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, + 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000, + 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000, + 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010, 0xf0000010, + 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, + 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, + 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, + 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x20000000, + 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, + 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202, 0x00000018, + 0x00000164, 0x00000408}}, + {1, 48, 2, 58, 0, 8, {0xe000002a, 0x20000043, 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, + 0x60000032, 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001, + 0x00000020, 0x00000003, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, + 0x80000040, 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043, + 0x40000022, 0x00000003, 0x40000042, 0xc0000043, 0xc0000022, 0x00000001, + 0x40000002, 0xc0000043, 0x40000062, 0x80000001, 0x40000042, 0x40000042, + 0x40000002, 0x00000002, 0x00000040, 0x80000002, 0x80000000, 0x80000002, + 0x80000040, 0x00000000, 0x80000040, 0x80000000, 0x00000040, 0x80000000, + 0x00000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000004, 0x00000080, 0x00000004, + 0x00000009, 0x00000101, 0x00000009, 0x00000012, 0x00000202, 0x0000001a, + 0x00000124, 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, + 0x00000590, 0x00001020}}, + {1, 49, 0, 58, 0, 9, {0x18000000, 0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, + 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, + 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, + 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010, + 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, + 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, + 0x90000010, 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000, + 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, + 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202, + 0x00000018, 0x00000164}}, + {1, 49, 2, 58, 0, 10, {0x60000000, 0xe000002a, 0x20000043, 0xb0000040, 0xd0000053, + 0xd0000022, 0x20000000, 0x60000032, 0x60000043, 0x20000040, + 0xe0000042, 0x60000002, 0x80000001, 0x00000020, 0x00000003, + 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, + 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043, + 0x40000022, 0x00000003, 0x40000042, 0xc0000043, 0xc0000022, + 0x00000001, 0x40000002, 0xc0000043, 0x40000062, 0x80000001, + 0x40000042, 0x40000042, 0x40000002, 0x00000002, 0x00000040, + 0x80000002, 0x80000000, 0x80000002, 0x80000040, 0x00000000, + 0x80000040, 0x80000000, 0x00000040, 0x80000000, 0x00000040, + 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000004, + 0x00000080, 0x00000004, 0x00000009, 0x00000101, 0x00000009, + 0x00000012, 0x00000202, 0x0000001a, 0x00000124, 0x0000040c, + 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, 0x00000590}}, + {1, 50, 0, 65, 0, 11, {0x0800000c, 0x18000000, 0xb800000a, 0xc8000010, 0x2c000010, + 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, + 0x08000010, 0xb8000010, 0x98000000, 0x60000000, 0x00000008, + 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000, + 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010, + 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, + 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, + 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000, + 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x20000000, + 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000040, + 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x00000049, + 0x00000103, 0x80000009, 0x80000012, 0x80000202, 0x00000018}}, + {1, 50, 2, 65, 0, 12, {0x20000030, 0x60000000, 0xe000002a, 0x20000043, 0xb0000040, + 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, 0x60000043, + 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x00000020, + 0x00000003, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, + 0x80000040, 0x20000001, 0x20000060, 0x80000001, 0x40000042, + 0xc0000043, 0x40000022, 0x00000003, 0x40000042, 0xc0000043, + 0xc0000022, 0x00000001, 0x40000002, 0xc0000043, 0x40000062, + 0x80000001, 0x40000042, 0x40000042, 0x40000002, 0x00000002, + 0x00000040, 0x80000002, 0x80000000, 0x80000002, 0x80000040, + 0x00000000, 0x80000040, 0x80000000, 0x00000040, 0x80000000, + 0x00000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000004, 0x00000080, 0x00000004, 0x00000009, 0x00000101, + 0x00000009, 0x00000012, 0x00000202, 0x0000001a, 0x00000124, + 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a, 0x00000060}}, + {1, 51, 0, 65, 0, 13, {0xe8000000, 0x0800000c, 0x18000000, 0xb800000a, 0xc8000010, + 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, + 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000, + 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, + 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, + 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, + 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, + 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, + 0x80000000, 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, + 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, + 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202}}, + {1, 51, 2, 65, 0, 14, {0xa0000003, 0x20000030, 0x60000000, 0xe000002a, 0x20000043, + 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, + 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001, + 0x00000020, 0x00000003, 0x40000052, 0x40000040, 0xe0000052, + 0xa0000000, 0x80000040, 0x20000001, 0x20000060, 0x80000001, + 0x40000042, 0xc0000043, 0x40000022, 0x00000003, 0x40000042, + 0xc0000043, 0xc0000022, 0x00000001, 0x40000002, 0xc0000043, + 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002, + 0x00000002, 0x00000040, 0x80000002, 0x80000000, 0x80000002, + 0x80000040, 0x00000000, 0x80000040, 0x80000000, 0x00000040, + 0x80000000, 0x00000040, 0x80000002, 0x00000000, 0x80000000, + 0x80000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, + 0x00000101, 0x00000009, 0x00000012, 0x00000202, 0x0000001a, + 0x00000124, 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a}}, + {1, 52, 0, 65, 0, 15, {0x04000010, 0xe8000000, 0x0800000c, 0x18000000, 0xb800000a, + 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, + 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, + 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, + 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x08000018, + 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, + 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, + 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, + 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000, + 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x20000000, 0x00000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, + 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012}}, + {2, 45, 0, 58, 0, 16, {0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, + 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, + 0x78000010, 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, + 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, + 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, 0x00000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x60000000, 0x00000018, 0xe0000000, + 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000, + 0xa0000000, 0x00000010, 0x80000000, 0x20000000, 0x20000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000041, + 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, + 0x80000107, 0x00000089, 0x00000014, 0x8000024b, 0x0000011b, + 0x8000016d, 0x8000041a, 0x000002e4, 0x80000054, 0x00000967}}, + {2, 46, 0, 58, 0, 17, {0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, + 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, + 0x08000018, 0x78000010, 0x08000014, 0x70000010, 0xb800001c, + 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, + 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, + 0x00000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x60000000, 0x00000018, + 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000, + 0x20000000, 0xa0000000, 0x00000010, 0x80000000, 0x20000000, + 0x20000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, + 0x4000004b, 0x80000107, 0x00000089, 0x00000014, 0x8000024b, + 0x0000011b, 0x8000016d, 0x8000041a, 0x000002e4, 0x80000054}}, + {2, 46, 2, 58, 0, 18, {0x90000070, 0xb0000053, 0x30000008, 0x00000043, 0xd0000072, + 0xb0000010, 0xf0000062, 0xc0000042, 0x00000030, 0xe0000042, + 0x20000060, 0xe0000041, 0x20000050, 0xc0000041, 0xe0000072, + 0xa0000003, 0xc0000012, 0x60000041, 0xc0000032, 0x20000001, + 0xc0000002, 0xe0000042, 0x60000042, 0x80000002, 0x00000000, + 0x00000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000, + 0x80000040, 0x80000000, 0x00000040, 0x80000001, 0x00000060, + 0x80000003, 0x40000002, 0xc0000040, 0xc0000002, 0x80000000, + 0x80000000, 0x80000002, 0x00000040, 0x00000002, 0x80000000, + 0x80000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000, + 0x80000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, + 0x00000105, 0x00000089, 0x00000016, 0x0000020b, 0x0000011b, + 0x0000012d, 0x0000041e, 0x00000224, 0x00000050, 0x0000092e, + 0x0000046c, 0x000005b6, 0x0000106a, 0x00000b90, 0x00000152}}, + {2, 47, 0, 58, 0, 19, {0x20000010, 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, + 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, + 0xb8000010, 0x08000018, 0x78000010, 0x08000014, 0x70000010, + 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, + 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, + 0x00000000, 0x00000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x60000000, + 0x00000018, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, + 0x20000000, 0x20000000, 0xa0000000, 0x00000010, 0x80000000, + 0x20000000, 0x20000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082, + 0xc0000046, 0x4000004b, 0x80000107, 0x00000089, 0x00000014, + 0x8000024b, 0x0000011b, 0x8000016d, 0x8000041a, 0x000002e4}}, + {2, 48, 0, 58, 0, 20, {0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, + 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, + 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, 0x08000014, + 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, + 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, + 0xa0000000, 0x00000000, 0x00000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x60000000, 0x00000018, 0xe0000000, 0x90000000, 0x30000010, + 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x00000010, + 0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000041, 0x40000022, 0x80000005, + 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, 0x00000089, + 0x00000014, 0x8000024b, 0x0000011b, 0x8000016d, 0x8000041a}}, + {2, 49, 0, 58, 0, 21, {0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, + 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, + 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, + 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, + 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, + 0x98000010, 0xa0000000, 0x00000000, 0x00000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x60000000, 0x00000018, 0xe0000000, 0x90000000, + 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, + 0x00000010, 0x80000000, 0x20000000, 0x20000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0xa0000000, + 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000020, 0x00000001, 0x40000002, 0x40000041, 0x40000022, + 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, + 0x00000089, 0x00000014, 0x8000024b, 0x0000011b, 0x8000016d}}, + {2, 49, 2, 58, 0, 22, {0xf0000010, 0xf000006a, 0x80000040, 0x90000070, 0xb0000053, + 0x30000008, 0x00000043, 0xd0000072, 0xb0000010, 0xf0000062, + 0xc0000042, 0x00000030, 0xe0000042, 0x20000060, 0xe0000041, + 0x20000050, 0xc0000041, 0xe0000072, 0xa0000003, 0xc0000012, + 0x60000041, 0xc0000032, 0x20000001, 0xc0000002, 0xe0000042, + 0x60000042, 0x80000002, 0x00000000, 0x00000000, 0x80000000, + 0x00000002, 0x00000040, 0x00000000, 0x80000040, 0x80000000, + 0x00000040, 0x80000001, 0x00000060, 0x80000003, 0x40000002, + 0xc0000040, 0xc0000002, 0x80000000, 0x80000000, 0x80000002, + 0x00000040, 0x00000002, 0x80000000, 0x80000000, 0x80000000, + 0x00000002, 0x00000040, 0x00000000, 0x80000040, 0x80000002, + 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000004, + 0x00000080, 0x00000004, 0x00000009, 0x00000105, 0x00000089, + 0x00000016, 0x0000020b, 0x0000011b, 0x0000012d, 0x0000041e, + 0x00000224, 0x00000050, 0x0000092e, 0x0000046c, 0x000005b6}}, + {2, 50, 0, 65, 0, 23, {0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, + 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, + 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, + 0x78000010, 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, + 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, + 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, 0x00000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x60000000, 0x00000018, 0xe0000000, + 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000, + 0xa0000000, 0x00000010, 0x80000000, 0x20000000, 0x20000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000041, + 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, + 0x80000107, 0x00000089, 0x00000014, 0x8000024b, 0x0000011b}}, + {2, 50, 2, 65, 0, 24, {0xd0000072, 0xf0000010, 0xf000006a, 0x80000040, 0x90000070, + 0xb0000053, 0x30000008, 0x00000043, 0xd0000072, 0xb0000010, + 0xf0000062, 0xc0000042, 0x00000030, 0xe0000042, 0x20000060, + 0xe0000041, 0x20000050, 0xc0000041, 0xe0000072, 0xa0000003, + 0xc0000012, 0x60000041, 0xc0000032, 0x20000001, 0xc0000002, + 0xe0000042, 0x60000042, 0x80000002, 0x00000000, 0x00000000, + 0x80000000, 0x00000002, 0x00000040, 0x00000000, 0x80000040, + 0x80000000, 0x00000040, 0x80000001, 0x00000060, 0x80000003, + 0x40000002, 0xc0000040, 0xc0000002, 0x80000000, 0x80000000, + 0x80000002, 0x00000040, 0x00000002, 0x80000000, 0x80000000, + 0x80000000, 0x00000002, 0x00000040, 0x00000000, 0x80000040, + 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000004, 0x00000080, 0x00000004, 0x00000009, 0x00000105, + 0x00000089, 0x00000016, 0x0000020b, 0x0000011b, 0x0000012d, + 0x0000041e, 0x00000224, 0x00000050, 0x0000092e, 0x0000046c}}, + {2, 51, 0, 65, 0, 25, {0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, + 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, + 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, + 0x08000018, 0x78000010, 0x08000014, 0x70000010, 0xb800001c, + 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, + 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, + 0x00000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x60000000, 0x00000018, + 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000, + 0x20000000, 0xa0000000, 0x00000010, 0x80000000, 0x20000000, + 0x20000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, + 0x4000004b, 0x80000107, 0x00000089, 0x00000014, 0x8000024b}}, + {2, 51, 2, 65, 0, 26, {0x00000043, 0xd0000072, 0xf0000010, 0xf000006a, 0x80000040, + 0x90000070, 0xb0000053, 0x30000008, 0x00000043, 0xd0000072, + 0xb0000010, 0xf0000062, 0xc0000042, 0x00000030, 0xe0000042, + 0x20000060, 0xe0000041, 0x20000050, 0xc0000041, 0xe0000072, + 0xa0000003, 0xc0000012, 0x60000041, 0xc0000032, 0x20000001, + 0xc0000002, 0xe0000042, 0x60000042, 0x80000002, 0x00000000, + 0x00000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000, + 0x80000040, 0x80000000, 0x00000040, 0x80000001, 0x00000060, + 0x80000003, 0x40000002, 0xc0000040, 0xc0000002, 0x80000000, + 0x80000000, 0x80000002, 0x00000040, 0x00000002, 0x80000000, + 0x80000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000, + 0x80000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, + 0x00000105, 0x00000089, 0x00000016, 0x0000020b, 0x0000011b, + 0x0000012d, 0x0000041e, 0x00000224, 0x00000050, 0x0000092e}}, + {2, 52, 0, 65, 0, 27, {0x0c000002, 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, + 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, + 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, + 0xb8000010, 0x08000018, 0x78000010, 0x08000014, 0x70000010, + 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, + 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, + 0x00000000, 0x00000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x60000000, + 0x00000018, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, + 0x20000000, 0x20000000, 0xa0000000, 0x00000010, 0x80000000, + 0x20000000, 0x20000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082, + 0xc0000046, 0x4000004b, 0x80000107, 0x00000089, 0x00000014}}, + {2, 53, 0, 65, 0, 28, {0xcc000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x3c000004, + 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, + 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, + 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, 0x08000014, + 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, + 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, + 0xa0000000, 0x00000000, 0x00000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x60000000, 0x00000018, 0xe0000000, 0x90000000, 0x30000010, + 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x00000010, + 0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000041, 0x40000022, 0x80000005, + 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, 0x00000089}}, + {2, 54, 0, 65, 0, 29, {0x0400001c, 0xcc000014, 0x0c000002, 0xc0000010, 0xb400001c, + 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, + 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, + 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, + 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, + 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, + 0x98000010, 0xa0000000, 0x00000000, 0x00000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x60000000, 0x00000018, 0xe0000000, 0x90000000, + 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, + 0x00000010, 0x80000000, 0x20000000, 0x20000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0xa0000000, + 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000020, 0x00000001, 0x40000002, 0x40000041, 0x40000022, + 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107}}, + {2, 55, 0, 65, 0, 30, {0x00000010, 0x0400001c, 0xcc000014, 0x0c000002, 0xc0000010, + 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, + 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, + 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, + 0x78000010, 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, + 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, + 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, 0x00000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x60000000, 0x00000018, 0xe0000000, + 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000, + 0xa0000000, 0x00000010, 0x80000000, 0x20000000, 0x20000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000041, + 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b}}, + {2, 56, 0, 65, 0, 31, {0x2600001a, 0x00000010, 0x0400001c, 0xcc000014, 0x0c000002, + 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, + 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, + 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, + 0x08000018, 0x78000010, 0x08000014, 0x70000010, 0xb800001c, + 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, + 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, + 0x00000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x60000000, 0x00000018, + 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000, + 0x20000000, 0xa0000000, 0x00000010, 0x80000000, 0x20000000, + 0x20000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046}}, + {0, 0, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}; +void +ubc_check(const uint32_t W[80], uint32_t dvmask[1]) +{ + uint32_t mask = ~((uint32_t)(0)); + mask &= (((((W[44] ^ W[45]) >> 29) & 1) - 1) | + ~(DV_I_48_0_bit | DV_I_51_0_bit | DV_I_52_0_bit | DV_II_45_0_bit | + DV_II_46_0_bit | DV_II_50_0_bit | DV_II_51_0_bit)); + mask &= (((((W[49] ^ W[50]) >> 29) & 1) - 1) | + ~(DV_I_46_0_bit | DV_II_45_0_bit | DV_II_50_0_bit | DV_II_51_0_bit | + DV_II_55_0_bit | DV_II_56_0_bit)); + mask &= (((((W[48] ^ W[49]) >> 29) & 1) - 1) | + ~(DV_I_45_0_bit | DV_I_52_0_bit | DV_II_49_0_bit | DV_II_50_0_bit | + DV_II_54_0_bit | DV_II_55_0_bit)); + mask &= ((((W[47] ^ (W[50] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit | + DV_II_51_0_bit | DV_II_56_0_bit)); + mask &= (((((W[47] ^ W[48]) >> 29) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_51_0_bit | DV_II_48_0_bit | DV_II_49_0_bit | + DV_II_53_0_bit | DV_II_54_0_bit)); + mask &= (((((W[46] >> 4) ^ (W[49] >> 29)) & 1) - 1) | + ~(DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit | DV_II_50_0_bit | + DV_II_55_0_bit)); + mask &= (((((W[46] ^ W[47]) >> 29) & 1) - 1) | + ~(DV_I_43_0_bit | DV_I_50_0_bit | DV_II_47_0_bit | DV_II_48_0_bit | + DV_II_52_0_bit | DV_II_53_0_bit)); + mask &= (((((W[45] >> 4) ^ (W[48] >> 29)) & 1) - 1) | + ~(DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit | DV_II_49_0_bit | + DV_II_54_0_bit)); + mask &= (((((W[45] ^ W[46]) >> 29) & 1) - 1) | + ~(DV_I_49_0_bit | DV_I_52_0_bit | DV_II_46_0_bit | DV_II_47_0_bit | + DV_II_51_0_bit | DV_II_52_0_bit)); + mask &= (((((W[44] >> 4) ^ (W[47] >> 29)) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit | DV_II_48_0_bit | + DV_II_53_0_bit)); + mask &= (((((W[43] >> 4) ^ (W[46] >> 29)) & 1) - 1) | + ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit | DV_II_47_0_bit | + DV_II_52_0_bit)); + mask &= (((((W[43] ^ W[44]) >> 29) & 1) - 1) | + ~(DV_I_47_0_bit | DV_I_50_0_bit | DV_I_51_0_bit | DV_II_45_0_bit | + DV_II_49_0_bit | DV_II_50_0_bit)); + mask &= (((((W[42] >> 4) ^ (W[45] >> 29)) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit | DV_I_52_0_bit | DV_II_46_0_bit | + DV_II_51_0_bit)); + mask &= (((((W[41] >> 4) ^ (W[44] >> 29)) & 1) - 1) | + ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit | DV_I_51_0_bit | DV_II_45_0_bit | + DV_II_50_0_bit)); + mask &= (((((W[40] ^ W[41]) >> 29) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_47_0_bit | DV_I_48_0_bit | DV_II_46_0_bit | + DV_II_47_0_bit | DV_II_56_0_bit)); + mask &= + (((((W[54] ^ W[55]) >> 29) & 1) - 1) | + ~(DV_I_51_0_bit | DV_II_47_0_bit | DV_II_50_0_bit | DV_II_55_0_bit | DV_II_56_0_bit)); + mask &= + (((((W[53] ^ W[54]) >> 29) & 1) - 1) | + ~(DV_I_50_0_bit | DV_II_46_0_bit | DV_II_49_0_bit | DV_II_54_0_bit | DV_II_55_0_bit)); + mask &= + (((((W[52] ^ W[53]) >> 29) & 1) - 1) | + ~(DV_I_49_0_bit | DV_II_45_0_bit | DV_II_48_0_bit | DV_II_53_0_bit | DV_II_54_0_bit)); + mask &= + ((((W[50] ^ (W[53] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_50_0_bit | DV_I_52_0_bit | DV_II_46_0_bit | DV_II_48_0_bit | DV_II_54_0_bit)); + mask &= + (((((W[50] ^ W[51]) >> 29) & 1) - 1) | + ~(DV_I_47_0_bit | DV_II_46_0_bit | DV_II_51_0_bit | DV_II_52_0_bit | DV_II_56_0_bit)); + mask &= + ((((W[49] ^ (W[52] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit | DV_II_47_0_bit | DV_II_53_0_bit)); + mask &= + ((((W[48] ^ (W[51] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit | DV_II_46_0_bit | DV_II_52_0_bit)); + mask &= + (((((W[42] ^ W[43]) >> 29) & 1) - 1) | + ~(DV_I_46_0_bit | DV_I_49_0_bit | DV_I_50_0_bit | DV_II_48_0_bit | DV_II_49_0_bit)); + mask &= + (((((W[41] ^ W[42]) >> 29) & 1) - 1) | + ~(DV_I_45_0_bit | DV_I_48_0_bit | DV_I_49_0_bit | DV_II_47_0_bit | DV_II_48_0_bit)); + mask &= + (((((W[40] >> 4) ^ (W[43] >> 29)) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_50_0_bit | DV_II_49_0_bit | DV_II_56_0_bit)); + mask &= + (((((W[39] >> 4) ^ (W[42] >> 29)) & 1) - 1) | + ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_49_0_bit | DV_II_48_0_bit | DV_II_55_0_bit)); + if (mask & + (DV_I_44_0_bit | DV_I_48_0_bit | DV_II_47_0_bit | DV_II_54_0_bit | DV_II_56_0_bit)) + mask &= (((((W[38] >> 4) ^ (W[41] >> 29)) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_48_0_bit | DV_II_47_0_bit | DV_II_54_0_bit | + DV_II_56_0_bit)); + mask &= + (((((W[37] >> 4) ^ (W[40] >> 29)) & 1) - 1) | + ~(DV_I_43_0_bit | DV_I_47_0_bit | DV_II_46_0_bit | DV_II_53_0_bit | DV_II_55_0_bit)); + if (mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_51_0_bit | DV_II_56_0_bit)) + mask &= (((((W[55] ^ W[56]) >> 29) & 1) - 1) | + ~(DV_I_52_0_bit | DV_II_48_0_bit | DV_II_51_0_bit | DV_II_56_0_bit)); + if (mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_50_0_bit | DV_II_56_0_bit)) + mask &= ((((W[52] ^ (W[55] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_52_0_bit | DV_II_48_0_bit | DV_II_50_0_bit | DV_II_56_0_bit)); + if (mask & (DV_I_51_0_bit | DV_II_47_0_bit | DV_II_49_0_bit | DV_II_55_0_bit)) + mask &= ((((W[51] ^ (W[54] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_51_0_bit | DV_II_47_0_bit | DV_II_49_0_bit | DV_II_55_0_bit)); + if (mask & (DV_I_48_0_bit | DV_II_47_0_bit | DV_II_52_0_bit | DV_II_53_0_bit)) + mask &= (((((W[51] ^ W[52]) >> 29) & 1) - 1) | + ~(DV_I_48_0_bit | DV_II_47_0_bit | DV_II_52_0_bit | DV_II_53_0_bit)); + if (mask & (DV_I_46_0_bit | DV_I_49_0_bit | DV_II_45_0_bit | DV_II_48_0_bit)) + mask &= (((((W[36] >> 4) ^ (W[40] >> 29)) & 1) - 1) | + ~(DV_I_46_0_bit | DV_I_49_0_bit | DV_II_45_0_bit | DV_II_48_0_bit)); + if (mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_49_0_bit)) + mask &= ((0 - (((W[53] ^ W[56]) >> 29) & 1)) | + ~(DV_I_52_0_bit | DV_II_48_0_bit | DV_II_49_0_bit)); + if (mask & (DV_I_50_0_bit | DV_II_46_0_bit | DV_II_47_0_bit)) + mask &= ((0 - (((W[51] ^ W[54]) >> 29) & 1)) | + ~(DV_I_50_0_bit | DV_II_46_0_bit | DV_II_47_0_bit)); + if (mask & (DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit)) + mask &= ((0 - (((W[50] ^ W[52]) >> 29) & 1)) | + ~(DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit)); + if (mask & (DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit)) + mask &= ((0 - (((W[49] ^ W[51]) >> 29) & 1)) | + ~(DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit)); + if (mask & (DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit)) + mask &= ((0 - (((W[48] ^ W[50]) >> 29) & 1)) | + ~(DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit)); + if (mask & (DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit)) + mask &= ((0 - (((W[47] ^ W[49]) >> 29) & 1)) | + ~(DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit)); + if (mask & (DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit)) + mask &= ((0 - (((W[46] ^ W[48]) >> 29) & 1)) | + ~(DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit)); + mask &= ((((W[45] ^ W[47]) & (1 << 6)) - (1 << 6)) | + ~(DV_I_47_2_bit | DV_I_49_2_bit | DV_I_51_2_bit)); + if (mask & (DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit)) + mask &= ((0 - (((W[45] ^ W[47]) >> 29) & 1)) | + ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit)); + mask &= + (((((W[44] ^ W[46]) >> 6) & 1) - 1) | ~(DV_I_46_2_bit | DV_I_48_2_bit | DV_I_50_2_bit)); + if (mask & (DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit)) + mask &= ((0 - (((W[44] ^ W[46]) >> 29) & 1)) | + ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit)); + mask &= ((0 - ((W[41] ^ (W[42] >> 5)) & (1 << 1))) | + ~(DV_I_48_2_bit | DV_II_46_2_bit | DV_II_51_2_bit)); + mask &= ((0 - ((W[40] ^ (W[41] >> 5)) & (1 << 1))) | + ~(DV_I_47_2_bit | DV_I_51_2_bit | DV_II_50_2_bit)); + if (mask & (DV_I_44_0_bit | DV_I_46_0_bit | DV_II_56_0_bit)) + mask &= ((0 - (((W[40] ^ W[42]) >> 4) & 1)) | + ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_II_56_0_bit)); + mask &= ((0 - ((W[39] ^ (W[40] >> 5)) & (1 << 1))) | + ~(DV_I_46_2_bit | DV_I_50_2_bit | DV_II_49_2_bit)); + if (mask & (DV_I_43_0_bit | DV_I_45_0_bit | DV_II_55_0_bit)) + mask &= ((0 - (((W[39] ^ W[41]) >> 4) & 1)) | + ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_II_55_0_bit)); + if (mask & (DV_I_44_0_bit | DV_II_54_0_bit | DV_II_56_0_bit)) + mask &= ((0 - (((W[38] ^ W[40]) >> 4) & 1)) | + ~(DV_I_44_0_bit | DV_II_54_0_bit | DV_II_56_0_bit)); + if (mask & (DV_I_43_0_bit | DV_II_53_0_bit | DV_II_55_0_bit)) + mask &= ((0 - (((W[37] ^ W[39]) >> 4) & 1)) | + ~(DV_I_43_0_bit | DV_II_53_0_bit | DV_II_55_0_bit)); + mask &= ((0 - ((W[36] ^ (W[37] >> 5)) & (1 << 1))) | + ~(DV_I_47_2_bit | DV_I_50_2_bit | DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit | DV_I_48_0_bit | DV_II_47_0_bit)) + mask &= (((((W[35] >> 4) ^ (W[39] >> 29)) & 1) - 1) | + ~(DV_I_45_0_bit | DV_I_48_0_bit | DV_II_47_0_bit)); + if (mask & (DV_I_48_0_bit | DV_II_48_0_bit)) + mask &= + ((0 - ((W[63] ^ (W[64] >> 5)) & (1 << 0))) | ~(DV_I_48_0_bit | DV_II_48_0_bit)); + if (mask & (DV_I_45_0_bit | DV_II_45_0_bit)) + mask &= + ((0 - ((W[63] ^ (W[64] >> 5)) & (1 << 1))) | ~(DV_I_45_0_bit | DV_II_45_0_bit)); + if (mask & (DV_I_47_0_bit | DV_II_47_0_bit)) + mask &= + ((0 - ((W[62] ^ (W[63] >> 5)) & (1 << 0))) | ~(DV_I_47_0_bit | DV_II_47_0_bit)); + if (mask & (DV_I_46_0_bit | DV_II_46_0_bit)) + mask &= + ((0 - ((W[61] ^ (W[62] >> 5)) & (1 << 0))) | ~(DV_I_46_0_bit | DV_II_46_0_bit)); + mask &= ((0 - ((W[61] ^ (W[62] >> 5)) & (1 << 2))) | ~(DV_I_46_2_bit | DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit | DV_II_45_0_bit)) + mask &= + ((0 - ((W[60] ^ (W[61] >> 5)) & (1 << 0))) | ~(DV_I_45_0_bit | DV_II_45_0_bit)); + if (mask & (DV_II_51_0_bit | DV_II_54_0_bit)) + mask &= (((((W[58] ^ W[59]) >> 29) & 1) - 1) | ~(DV_II_51_0_bit | DV_II_54_0_bit)); + if (mask & (DV_II_50_0_bit | DV_II_53_0_bit)) + mask &= (((((W[57] ^ W[58]) >> 29) & 1) - 1) | ~(DV_II_50_0_bit | DV_II_53_0_bit)); + if (mask & (DV_II_52_0_bit | DV_II_54_0_bit)) + mask &= ((((W[56] ^ (W[59] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_II_52_0_bit | DV_II_54_0_bit)); + if (mask & (DV_II_51_0_bit | DV_II_52_0_bit)) + mask &= ((0 - (((W[56] ^ W[59]) >> 29) & 1)) | ~(DV_II_51_0_bit | DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit | DV_II_52_0_bit)) + mask &= (((((W[56] ^ W[57]) >> 29) & 1) - 1) | ~(DV_II_49_0_bit | DV_II_52_0_bit)); + if (mask & (DV_II_51_0_bit | DV_II_53_0_bit)) + mask &= ((((W[55] ^ (W[58] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_II_51_0_bit | DV_II_53_0_bit)); + if (mask & (DV_II_50_0_bit | DV_II_52_0_bit)) + mask &= ((((W[54] ^ (W[57] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_II_50_0_bit | DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit | DV_II_51_0_bit)) + mask &= ((((W[53] ^ (W[56] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_II_49_0_bit | DV_II_51_0_bit)); + mask &= + ((((W[51] ^ (W[50] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_50_2_bit | DV_II_46_2_bit)); + mask &= ((((W[48] ^ W[50]) & (1 << 6)) - (1 << 6)) | ~(DV_I_50_2_bit | DV_II_46_2_bit)); + if (mask & (DV_I_51_0_bit | DV_I_52_0_bit)) + mask &= ((0 - (((W[48] ^ W[55]) >> 29) & 1)) | ~(DV_I_51_0_bit | DV_I_52_0_bit)); + mask &= ((((W[47] ^ W[49]) & (1 << 6)) - (1 << 6)) | ~(DV_I_49_2_bit | DV_I_51_2_bit)); + mask &= + ((((W[48] ^ (W[47] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_47_2_bit | DV_II_51_2_bit)); + mask &= ((((W[46] ^ W[48]) & (1 << 6)) - (1 << 6)) | ~(DV_I_48_2_bit | DV_I_50_2_bit)); + mask &= + ((((W[47] ^ (W[46] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_46_2_bit | DV_II_50_2_bit)); + mask &= ((0 - ((W[44] ^ (W[45] >> 5)) & (1 << 1))) | ~(DV_I_51_2_bit | DV_II_49_2_bit)); + mask &= ((((W[43] ^ W[45]) & (1 << 6)) - (1 << 6)) | ~(DV_I_47_2_bit | DV_I_49_2_bit)); + mask &= (((((W[42] ^ W[44]) >> 6) & 1) - 1) | ~(DV_I_46_2_bit | DV_I_48_2_bit)); + mask &= + ((((W[43] ^ (W[42] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_II_46_2_bit | DV_II_51_2_bit)); + mask &= + ((((W[42] ^ (W[41] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_51_2_bit | DV_II_50_2_bit)); + mask &= + ((((W[41] ^ (W[40] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_50_2_bit | DV_II_49_2_bit)); + if (mask & (DV_I_52_0_bit | DV_II_51_0_bit)) + mask &= ((((W[39] ^ (W[43] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_52_0_bit | DV_II_51_0_bit)); + if (mask & (DV_I_51_0_bit | DV_II_50_0_bit)) + mask &= ((((W[38] ^ (W[42] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_51_0_bit | DV_II_50_0_bit)); + if (mask & (DV_I_48_2_bit | DV_I_51_2_bit)) + mask &= ((0 - ((W[37] ^ (W[38] >> 5)) & (1 << 1))) | ~(DV_I_48_2_bit | DV_I_51_2_bit)); + if (mask & (DV_I_50_0_bit | DV_II_49_0_bit)) + mask &= ((((W[37] ^ (W[41] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_50_0_bit | DV_II_49_0_bit)); + if (mask & (DV_II_52_0_bit | DV_II_54_0_bit)) + mask &= ((0 - ((W[36] ^ W[38]) & (1 << 4))) | ~(DV_II_52_0_bit | DV_II_54_0_bit)); + mask &= ((0 - ((W[35] ^ (W[36] >> 5)) & (1 << 1))) | ~(DV_I_46_2_bit | DV_I_49_2_bit)); + if (mask & (DV_I_51_0_bit | DV_II_47_0_bit)) + mask &= ((((W[35] ^ (W[39] >> 25)) & (1 << 3)) - (1 << 3)) | + ~(DV_I_51_0_bit | DV_II_47_0_bit)); + if (mask) { + if (mask & DV_I_43_0_bit) + if (!((W[61] ^ (W[62] >> 5)) & (1 << 1)) || + !(!((W[59] ^ (W[63] >> 25)) & (1 << 5))) || + !((W[58] ^ (W[63] >> 30)) & (1 << 0))) + mask &= ~DV_I_43_0_bit; + if (mask & DV_I_44_0_bit) + if (!((W[62] ^ (W[63] >> 5)) & (1 << 1)) || + !(!((W[60] ^ (W[64] >> 25)) & (1 << 5))) || + !((W[59] ^ (W[64] >> 30)) & (1 << 0))) + mask &= ~DV_I_44_0_bit; + if (mask & DV_I_46_2_bit) + mask &= ((~((W[40] ^ W[42]) >> 2)) | ~DV_I_46_2_bit); + if (mask & DV_I_47_2_bit) + if (!((W[62] ^ (W[63] >> 5)) & (1 << 2)) || !(!((W[41] ^ W[43]) & (1 << 6)))) + mask &= ~DV_I_47_2_bit; + if (mask & DV_I_48_2_bit) + if (!((W[63] ^ (W[64] >> 5)) & (1 << 2)) || + !(!((W[48] ^ (W[49] << 5)) & (1 << 6)))) + mask &= ~DV_I_48_2_bit; + if (mask & DV_I_49_2_bit) + if (!(!((W[49] ^ (W[50] << 5)) & (1 << 6))) || !((W[42] ^ W[50]) & (1 << 1)) || + !(!((W[39] ^ (W[40] << 5)) & (1 << 6))) || !((W[38] ^ W[40]) & (1 << 1))) + mask &= ~DV_I_49_2_bit; + if (mask & DV_I_50_0_bit) + mask &= ((((W[36] ^ W[37]) << 7)) | ~DV_I_50_0_bit); + if (mask & DV_I_50_2_bit) + mask &= ((((W[43] ^ W[51]) << 11)) | ~DV_I_50_2_bit); + if (mask & DV_I_51_0_bit) + mask &= ((((W[37] ^ W[38]) << 9)) | ~DV_I_51_0_bit); + if (mask & DV_I_51_2_bit) + if (!(!((W[51] ^ (W[52] << 5)) & (1 << 6))) || !(!((W[49] ^ W[51]) & (1 << 6))) || + !(!((W[37] ^ (W[37] >> 5)) & (1 << 1))) || + !(!((W[35] ^ (W[39] >> 25)) & (1 << 5)))) + mask &= ~DV_I_51_2_bit; + if (mask & DV_I_52_0_bit) + mask &= ((((W[38] ^ W[39]) << 11)) | ~DV_I_52_0_bit); + if (mask & DV_II_46_2_bit) + mask &= ((((W[47] ^ W[51]) << 17)) | ~DV_II_46_2_bit); + if (mask & DV_II_48_0_bit) + if (!(!((W[36] ^ (W[40] >> 25)) & (1 << 3))) || + !((W[35] ^ (W[40] << 2)) & (1 << 30))) + mask &= ~DV_II_48_0_bit; + if (mask & DV_II_49_0_bit) + if (!(!((W[37] ^ (W[41] >> 25)) & (1 << 3))) || + !((W[36] ^ (W[41] << 2)) & (1 << 30))) + mask &= ~DV_II_49_0_bit; + if (mask & DV_II_49_2_bit) + if (!(!((W[53] ^ (W[54] << 5)) & (1 << 6))) || !(!((W[51] ^ W[53]) & (1 << 6))) || + !((W[50] ^ W[54]) & (1 << 1)) || !(!((W[45] ^ (W[46] << 5)) & (1 << 6))) || + !(!((W[37] ^ (W[41] >> 25)) & (1 << 5))) || + !((W[36] ^ (W[41] >> 30)) & (1 << 0))) + mask &= ~DV_II_49_2_bit; + if (mask & DV_II_50_0_bit) + if (!((W[55] ^ W[58]) & (1 << 29)) || !(!((W[38] ^ (W[42] >> 25)) & (1 << 3))) || + !((W[37] ^ (W[42] << 2)) & (1 << 30))) + mask &= ~DV_II_50_0_bit; + if (mask & DV_II_50_2_bit) + if (!(!((W[54] ^ (W[55] << 5)) & (1 << 6))) || !(!((W[52] ^ W[54]) & (1 << 6))) || + !((W[51] ^ W[55]) & (1 << 1)) || !((W[45] ^ W[47]) & (1 << 1)) || + !(!((W[38] ^ (W[42] >> 25)) & (1 << 5))) || + !((W[37] ^ (W[42] >> 30)) & (1 << 0))) + mask &= ~DV_II_50_2_bit; + if (mask & DV_II_51_0_bit) + if (!(!((W[39] ^ (W[43] >> 25)) & (1 << 3))) || + !((W[38] ^ (W[43] << 2)) & (1 << 30))) + mask &= ~DV_II_51_0_bit; + if (mask & DV_II_51_2_bit) + if (!(!((W[55] ^ (W[56] << 5)) & (1 << 6))) || !(!((W[53] ^ W[55]) & (1 << 6))) || + !((W[52] ^ W[56]) & (1 << 1)) || !((W[46] ^ W[48]) & (1 << 1)) || + !(!((W[39] ^ (W[43] >> 25)) & (1 << 5))) || + !((W[38] ^ (W[43] >> 30)) & (1 << 0))) + mask &= ~DV_II_51_2_bit; + if (mask & DV_II_52_0_bit) + if (!(!((W[59] ^ W[60]) & (1 << 29))) || + !(!((W[40] ^ (W[44] >> 25)) & (1 << 3))) || + !(!((W[40] ^ (W[44] >> 25)) & (1 << 4))) || + !((W[39] ^ (W[44] << 2)) & (1 << 30))) + mask &= ~DV_II_52_0_bit; + if (mask & DV_II_53_0_bit) + if (!((W[58] ^ W[61]) & (1 << 29)) || !(!((W[57] ^ (W[61] >> 25)) & (1 << 4))) || + !(!((W[41] ^ (W[45] >> 25)) & (1 << 3))) || + !(!((W[41] ^ (W[45] >> 25)) & (1 << 4)))) + mask &= ~DV_II_53_0_bit; + if (mask & DV_II_54_0_bit) + if (!(!((W[58] ^ (W[62] >> 25)) & (1 << 4))) || + !(!((W[42] ^ (W[46] >> 25)) & (1 << 3))) || + !(!((W[42] ^ (W[46] >> 25)) & (1 << 4)))) + mask &= ~DV_II_54_0_bit; + if (mask & DV_II_55_0_bit) + if (!(!((W[59] ^ (W[63] >> 25)) & (1 << 4))) || + !(!((W[57] ^ (W[59] >> 25)) & (1 << 4))) || + !(!((W[43] ^ (W[47] >> 25)) & (1 << 3))) || + !(!((W[43] ^ (W[47] >> 25)) & (1 << 4)))) + mask &= ~DV_II_55_0_bit; + if (mask & DV_II_56_0_bit) + if (!(!((W[60] ^ (W[64] >> 25)) & (1 << 4))) || + !(!((W[44] ^ (W[48] >> 25)) & (1 << 3))) || + !(!((W[44] ^ (W[48] >> 25)) & (1 << 4)))) + mask &= ~DV_II_56_0_bit; + } + + dvmask[0] = mask; +} + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#endif diff --git a/src/lib/crypto/sha1cd/ubc_check.h b/src/lib/crypto/sha1cd/ubc_check.h new file mode 100644 index 0000000..a43c7b6 --- /dev/null +++ b/src/lib/crypto/sha1cd/ubc_check.h @@ -0,0 +1,62 @@ +/*** + * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com> + * Distributed under the MIT Software License. + * See accompanying file LICENSE.txt or copy at + * https://opensource.org/licenses/MIT + ***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable +bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions +for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +*/ + +#ifndef SHA1DC_UBC_CHECK_H +#define SHA1DC_UBC_CHECK_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <stdint.h> +#endif + +#define DVMASKSIZE 1 +typedef struct { + int dvType; + int dvK; + int dvB; + int testt; + int maski; + int maskb; + uint32_t dm[80]; +} dv_info_t; +extern dv_info_t sha1_dvs[]; +void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]); + +#define DOSTORESTATE58 +#define DOSTORESTATE65 + +#define CHECK_DVMASK(_DVMASK) (0 != _DVMASK[0]) + +#if defined(__cplusplus) +} +#endif + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#endif + +#endif diff --git a/src/lib/crypto/signatures.cpp b/src/lib/crypto/signatures.cpp new file mode 100644 index 0000000..ea39935 --- /dev/null +++ b/src/lib/crypto/signatures.cpp @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include "crypto/signatures.h" +#include "librepgp/stream-packet.h" +#include "librepgp/stream-sig.h" +#include "utils.h" +#include "sec_profile.hpp" + +/** + * @brief Add signature fields to the hash context and finish it. + * @param hash initialized hash context fed with signed data (document, key, etc). + * It is finalized in this function. + * @param sig populated or loaded signature + * @param hbuf buffer to store the resulting hash. Must be large enough for hash output. + * @param hlen on success will be filled with the hash size, otherwise zeroed + * @return RNP_SUCCESS on success or some error otherwise + */ +static void +signature_hash_finish(const pgp_signature_t &sig, rnp::Hash &hash, uint8_t *hbuf, size_t &hlen) +{ + hash.add(sig.hashed_data, sig.hashed_len); + if (sig.version > PGP_V3) { + uint8_t trailer[6] = {0x04, 0xff, 0x00, 0x00, 0x00, 0x00}; + STORE32BE(&trailer[2], sig.hashed_len); + hash.add(trailer, 6); + } + hlen = hash.finish(hbuf); +} + +std::unique_ptr<rnp::Hash> +signature_init(const pgp_key_material_t &key, pgp_hash_alg_t hash_alg) +{ + auto hash = rnp::Hash::create(hash_alg); + if (key.alg == PGP_PKA_SM2) { +#if defined(ENABLE_SM2) + rnp_result_t r = sm2_compute_za(key.ec, *hash); + if (r != RNP_SUCCESS) { + RNP_LOG("failed to compute SM2 ZA field"); + throw rnp::rnp_exception(r); + } +#else + RNP_LOG("SM2 ZA computation not available"); + throw rnp::rnp_exception(RNP_ERROR_NOT_IMPLEMENTED); +#endif + } + return hash; +} + +void +signature_calculate(pgp_signature_t & sig, + pgp_key_material_t & seckey, + rnp::Hash & hash, + rnp::SecurityContext &ctx) +{ + uint8_t hval[PGP_MAX_HASH_SIZE]; + size_t hlen = 0; + rnp_result_t ret = RNP_ERROR_GENERIC; + const pgp_hash_alg_t hash_alg = hash.alg(); + + /* Finalize hash first, since function is required to do this */ + try { + signature_hash_finish(sig, hash, hval, hlen); + } catch (const std::exception &e) { + RNP_LOG("Failed to finalize hash: %s", e.what()); + throw; + } + + if (!seckey.secret) { + RNP_LOG("Secret key is required."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (sig.palg != seckey.alg) { + RNP_LOG("Signature and secret key do not agree on algorithm type."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + /* Validate key material if didn't before */ + seckey.validate(ctx, false); + if (!seckey.valid()) { + RNP_LOG("Attempt to sign with invalid key material."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + /* copy left 16 bits to signature */ + memcpy(sig.lbits, hval, 2); + + /* sign */ + pgp_signature_material_t material = {}; + switch (sig.palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + ret = rsa_sign_pkcs1(&ctx.rng, &material.rsa, sig.halg, hval, hlen, &seckey.rsa); + if (ret) { + RNP_LOG("rsa signing failed"); + } + break; + case PGP_PKA_EDDSA: + ret = eddsa_sign(&ctx.rng, &material.ecc, hval, hlen, &seckey.ec); + if (ret) { + RNP_LOG("eddsa signing failed"); + } + break; + case PGP_PKA_DSA: + ret = dsa_sign(&ctx.rng, &material.dsa, hval, hlen, &seckey.dsa); + if (ret != RNP_SUCCESS) { + RNP_LOG("DSA signing failed"); + } + break; + /* + * ECDH is signed with ECDSA. This must be changed when ECDH will support + * X25519, but I need to check how it should be done exactly. + */ + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: { + const ec_curve_desc_t *curve = get_curve_desc(seckey.ec.curve); + if (!curve) { + RNP_LOG("Unknown curve"); + ret = RNP_ERROR_BAD_PARAMETERS; + break; + } + if (!curve_supported(seckey.ec.curve)) { + RNP_LOG("EC sign: curve %s is not supported.", curve->pgp_name); + ret = RNP_ERROR_NOT_SUPPORTED; + break; + } + /* "-2" because ECDSA on P-521 must work with SHA-512 digest */ + if (BITS_TO_BYTES(curve->bitlen) - 2 > hlen) { + RNP_LOG("Message hash too small"); + ret = RNP_ERROR_BAD_PARAMETERS; + break; + } + + if (sig.palg == PGP_PKA_SM2) { +#if defined(ENABLE_SM2) + ret = sm2_sign(&ctx.rng, &material.ecc, hash_alg, hval, hlen, &seckey.ec); + if (ret) { + RNP_LOG("SM2 signing failed"); + } +#else + RNP_LOG("SM2 signing is not available."); + ret = RNP_ERROR_NOT_IMPLEMENTED; +#endif + break; + } + + ret = ecdsa_sign(&ctx.rng, &material.ecc, hash_alg, hval, hlen, &seckey.ec); + if (ret) { + RNP_LOG("ECDSA signing failed"); + } + break; + } + default: + RNP_LOG("Unsupported algorithm %d", sig.palg); + break; + } + if (ret) { + throw rnp::rnp_exception(ret); + } + try { + sig.write_material(material); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + throw; + } +} + +rnp_result_t +signature_validate(const pgp_signature_t & sig, + const pgp_key_material_t & key, + rnp::Hash & hash, + const rnp::SecurityContext &ctx) +{ + if (sig.palg != key.alg) { + RNP_LOG("Signature and key do not agree on algorithm type: %d vs %d", + (int) sig.palg, + (int) key.alg); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* Check signature security */ + auto action = + sig.is_document() ? rnp::SecurityAction::VerifyData : rnp::SecurityAction::VerifyKey; + if (ctx.profile.hash_level(sig.halg, sig.creation(), action) < + rnp::SecurityLevel::Default) { + RNP_LOG("Insecure hash algorithm %d, marking signature as invalid.", sig.halg); + return RNP_ERROR_SIGNATURE_INVALID; + } + + /* Finalize hash */ + uint8_t hval[PGP_MAX_HASH_SIZE]; + size_t hlen = 0; + try { + signature_hash_finish(sig, hash, hval, hlen); + } catch (const std::exception &e) { + RNP_LOG("Failed to finalize signature hash."); + return RNP_ERROR_GENERIC; + } + + /* compare lbits */ + if (memcmp(hval, sig.lbits, 2)) { + RNP_LOG("wrong lbits"); + return RNP_ERROR_SIGNATURE_INVALID; + } + + /* validate signature */ + pgp_signature_material_t material = {}; + try { + sig.parse_material(material); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = RNP_ERROR_GENERIC; + switch (sig.palg) { + case PGP_PKA_DSA: + ret = dsa_verify(&material.dsa, hval, hlen, &key.dsa); + break; + case PGP_PKA_EDDSA: + ret = eddsa_verify(&material.ecc, hval, hlen, &key.ec); + break; + case PGP_PKA_SM2: +#if defined(ENABLE_SM2) + ret = sm2_verify(&material.ecc, hash.alg(), hval, hlen, &key.ec); +#else + RNP_LOG("SM2 verification is not available."); + ret = RNP_ERROR_NOT_IMPLEMENTED; +#endif + break; + case PGP_PKA_RSA: + case PGP_PKA_RSA_SIGN_ONLY: + ret = rsa_verify_pkcs1(&material.rsa, sig.halg, hval, hlen, &key.rsa); + break; + case PGP_PKA_RSA_ENCRYPT_ONLY: + RNP_LOG("RSA encrypt-only signature considered as invalid."); + ret = RNP_ERROR_SIGNATURE_INVALID; + break; + case PGP_PKA_ECDSA: + if (!curve_supported(key.ec.curve)) { + RNP_LOG("ECDSA verify: curve %d is not supported.", (int) key.ec.curve); + ret = RNP_ERROR_NOT_SUPPORTED; + break; + } + ret = ecdsa_verify(&material.ecc, hash.alg(), hval, hlen, &key.ec); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + RNP_LOG("ElGamal are considered as invalid."); + ret = RNP_ERROR_SIGNATURE_INVALID; + break; + default: + RNP_LOG("Unknown algorithm"); + ret = RNP_ERROR_BAD_PARAMETERS; + } + return ret; +} diff --git a/src/lib/crypto/signatures.h b/src/lib/crypto/signatures.h new file mode 100644 index 0000000..6ba64ce --- /dev/null +++ b/src/lib/crypto/signatures.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_SIGNATURES_H_ +#define RNP_SIGNATURES_H_ + +#include "crypto/hash.hpp" + +/** + * @brief Initialize a signature computation. + * @param key the key that will be used to sign or verify + * @param hash_alg the digest algo to be used + * @param hash digest object that will be initialized + */ +std::unique_ptr<rnp::Hash> signature_init(const pgp_key_material_t &key, + pgp_hash_alg_t hash_alg); + +/** + * @brief Calculate signature with pre-populated hash + * @param sig signature to calculate + * @param seckey signing secret key material + * @param hash pre-populated with signed data hash context. It is finalized and destroyed + * during the execution. Signature fields and trailer are hashed in this function. + * @param rng random number generator + */ +void signature_calculate(pgp_signature_t & sig, + pgp_key_material_t & seckey, + rnp::Hash & hash, + rnp::SecurityContext &ctx); + +/** + * @brief Validate a signature with pre-populated hash. This method just checks correspondence + * between the hash and signature material. Expiration time and other fields are not + * checked for validity. + * @param sig signature to validate + * @param key public key material of the verifying key + * @param hash pre-populated with signed data hash context. It is finalized + * during the execution. Signature fields and trailer are hashed in this function. + * @return RNP_SUCCESS if signature was successfully validated or error code otherwise. + */ +rnp_result_t signature_validate(const pgp_signature_t & sig, + const pgp_key_material_t & key, + rnp::Hash & hash, + const rnp::SecurityContext &ctx); + +#endif diff --git a/src/lib/crypto/sm2.cpp b/src/lib/crypto/sm2.cpp new file mode 100644 index 0000000..2af537d --- /dev/null +++ b/src/lib/crypto/sm2.cpp @@ -0,0 +1,383 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <botan/ffi.h> +#include "hash_botan.hpp" +#include "sm2.h" +#include "utils.h" +#include "bn.h" + +static bool +sm2_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *keydata) +{ + const ec_curve_desc_t *curve = NULL; + botan_mp_t px = NULL; + botan_mp_t py = NULL; + size_t sz; + bool res = false; + + if (!(curve = get_curve_desc(keydata->curve))) { + return false; + } + + const size_t sign_half_len = BITS_TO_BYTES(curve->bitlen); + sz = mpi_bytes(&keydata->p); + if (!sz || (sz != (2 * sign_half_len + 1)) || (keydata->p.mpi[0] != 0x04)) { + goto end; + } + + if (botan_mp_init(&px) || botan_mp_init(&py) || + botan_mp_from_bin(px, &keydata->p.mpi[1], sign_half_len) || + botan_mp_from_bin(py, &keydata->p.mpi[1 + sign_half_len], sign_half_len)) { + goto end; + } + res = !botan_pubkey_load_sm2(pubkey, px, py, curve->botan_name); +end: + botan_mp_destroy(px); + botan_mp_destroy(py); + return res; +} + +static bool +sm2_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *keydata) +{ + const ec_curve_desc_t *curve = NULL; + bignum_t * x = NULL; + bool res = false; + + if (!(curve = get_curve_desc(keydata->curve))) { + return false; + } + if (!(x = mpi2bn(&keydata->x))) { + return false; + } + res = !botan_privkey_load_sm2(seckey, BN_HANDLE_PTR(x), curve->botan_name); + bn_free(x); + return res; +} + +rnp_result_t +sm2_compute_za(const pgp_ec_key_t &key, rnp::Hash &hash, const char *ident_field) +{ + rnp_result_t result = RNP_ERROR_GENERIC; + botan_pubkey_t sm2_key = NULL; + int rc; + + const char *hash_algo = rnp::Hash_Botan::name_backend(hash.alg()); + size_t digest_len = hash.size(); + + uint8_t *digest_buf = (uint8_t *) malloc(digest_len); + if (!digest_buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (!sm2_load_public_key(&sm2_key, &key)) { + RNP_LOG("Failed to load SM2 key"); + goto done; + } + + if (ident_field == NULL) + ident_field = "1234567812345678"; + + rc = botan_pubkey_sm2_compute_za(digest_buf, &digest_len, ident_field, hash_algo, sm2_key); + + if (rc != 0) { + RNP_LOG("compute_za failed %d", rc); + goto done; + } + + try { + hash.add(digest_buf, digest_len); + } catch (const std::exception &e) { + RNP_LOG("Failed to update hash: %s", e.what()); + goto done; + } + + result = RNP_SUCCESS; +done: + free(digest_buf); + botan_pubkey_destroy(sm2_key); + return result; +} + +rnp_result_t +sm2_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + if (!sm2_load_public_key(&bpkey, key) || botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + if (!sm2_load_secret_key(&bskey, key) || + botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_privkey_destroy(bskey); + botan_pubkey_destroy(bpkey); + return ret; +} + +rnp_result_t +sm2_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + const ec_curve_desc_t *curve = NULL; + botan_pk_op_sign_t signer = NULL; + botan_privkey_t b_key = NULL; + uint8_t out_buf[2 * MAX_CURVE_BYTELEN] = {0}; + size_t sign_half_len = 0; + size_t sig_len = 0; + rnp_result_t ret = RNP_ERROR_SIGNING_FAILED; + + if (botan_ffi_supports_api(20180713) != 0) { + RNP_LOG("SM2 signatures requires Botan 2.8 or higher"); + return RNP_ERROR_NOT_SUPPORTED; + } + + if (hash_len != rnp::Hash::size(hash_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!(curve = get_curve_desc(key->curve))) { + return RNP_ERROR_BAD_PARAMETERS; + } + sign_half_len = BITS_TO_BYTES(curve->bitlen); + sig_len = 2 * sign_half_len; + + if (!sm2_load_secret_key(&b_key, key)) { + RNP_LOG("Can't load private key"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + + if (botan_pk_op_sign_create(&signer, b_key, ",Raw", 0)) { + goto end; + } + + if (botan_pk_op_sign_update(signer, hash, hash_len)) { + goto end; + } + + if (botan_pk_op_sign_finish(signer, rng->handle(), out_buf, &sig_len)) { + RNP_LOG("Signing failed"); + goto end; + } + + // Allocate memory and copy results + if (mem2mpi(&sig->r, out_buf, sign_half_len) && + mem2mpi(&sig->s, out_buf + sign_half_len, sign_half_len)) { + // All good now + ret = RNP_SUCCESS; + } +end: + botan_privkey_destroy(b_key); + botan_pk_op_sign_destroy(signer); + return ret; +} + +rnp_result_t +sm2_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + const ec_curve_desc_t *curve = NULL; + botan_pubkey_t pub = NULL; + botan_pk_op_verify_t verifier = NULL; + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + uint8_t sign_buf[2 * MAX_CURVE_BYTELEN] = {0}; + size_t r_blen, s_blen, sign_half_len; + + if (botan_ffi_supports_api(20180713) != 0) { + RNP_LOG("SM2 signatures requires Botan 2.8 or higher"); + return RNP_ERROR_NOT_SUPPORTED; + } + + if (hash_len != rnp::Hash::size(hash_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + curve = get_curve_desc(key->curve); + if (curve == NULL) { + return RNP_ERROR_BAD_PARAMETERS; + } + sign_half_len = BITS_TO_BYTES(curve->bitlen); + + if (!sm2_load_public_key(&pub, key)) { + RNP_LOG("Failed to load public key"); + goto end; + } + + if (botan_pk_op_verify_create(&verifier, pub, ",Raw", 0)) { + goto end; + } + + if (botan_pk_op_verify_update(verifier, hash, hash_len)) { + goto end; + } + + r_blen = sig->r.len; + s_blen = sig->s.len; + if (!r_blen || (r_blen > sign_half_len) || !s_blen || (s_blen > sign_half_len) || + (sign_half_len > MAX_CURVE_BYTELEN)) { + goto end; + } + + mpi2mem(&sig->r, sign_buf + sign_half_len - r_blen); + mpi2mem(&sig->s, sign_buf + 2 * sign_half_len - s_blen); + + if (!botan_pk_op_verify_finish(verifier, sign_buf, sign_half_len * 2)) { + ret = RNP_SUCCESS; + } +end: + botan_pubkey_destroy(pub); + botan_pk_op_verify_destroy(verifier); + return ret; +} + +rnp_result_t +sm2_encrypt(rnp::RNG * rng, + pgp_sm2_encrypted_t *out, + const uint8_t * in, + size_t in_len, + pgp_hash_alg_t hash_algo, + const pgp_ec_key_t * key) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + const ec_curve_desc_t *curve = NULL; + botan_pubkey_t sm2_key = NULL; + botan_pk_op_encrypt_t enc_op = NULL; + size_t point_len; + size_t hash_alg_len; + size_t ctext_len; + + curve = get_curve_desc(key->curve); + if (curve == NULL) { + return RNP_ERROR_GENERIC; + } + point_len = BITS_TO_BYTES(curve->bitlen); + hash_alg_len = rnp::Hash::size(hash_algo); + if (!hash_alg_len) { + RNP_LOG("Unknown hash algorithm for SM2 encryption"); + goto done; + } + + /* + * Format of SM2 ciphertext is a point (2*point_len+1) plus + * the masked ciphertext (out_len) plus a hash. + */ + ctext_len = (2 * point_len + 1) + in_len + hash_alg_len; + if (ctext_len > PGP_MPINT_SIZE) { + RNP_LOG("too large output for SM2 encryption"); + goto done; + } + + if (!sm2_load_public_key(&sm2_key, key)) { + RNP_LOG("Failed to load public key"); + goto done; + } + + /* + SM2 encryption doesn't have any kind of format specifier because + it's an all in one scheme, only the hash (used for the integrity + check) is specified. + */ + if (botan_pk_op_encrypt_create( + &enc_op, sm2_key, rnp::Hash_Botan::name_backend(hash_algo), 0)) { + goto done; + } + + out->m.len = sizeof(out->m.mpi); + if (botan_pk_op_encrypt(enc_op, rng->handle(), out->m.mpi, &out->m.len, in, in_len) == 0) { + out->m.mpi[out->m.len++] = hash_algo; + ret = RNP_SUCCESS; + } +done: + botan_pk_op_encrypt_destroy(enc_op); + botan_pubkey_destroy(sm2_key); + return ret; +} + +rnp_result_t +sm2_decrypt(uint8_t * out, + size_t * out_len, + const pgp_sm2_encrypted_t *in, + const pgp_ec_key_t * key) +{ + const ec_curve_desc_t *curve; + botan_pk_op_decrypt_t decrypt_op = NULL; + botan_privkey_t b_key = NULL; + size_t in_len; + rnp_result_t ret = RNP_ERROR_GENERIC; + uint8_t hash_id; + const char * hash_name = NULL; + + curve = get_curve_desc(key->curve); + in_len = mpi_bytes(&in->m); + if (curve == NULL || in_len < 64) { + goto done; + } + + if (!sm2_load_secret_key(&b_key, key)) { + RNP_LOG("Can't load private key"); + goto done; + } + + hash_id = in->m.mpi[in_len - 1]; + hash_name = rnp::Hash_Botan::name_backend((pgp_hash_alg_t) hash_id); + if (!hash_name) { + RNP_LOG("Unknown hash used in SM2 ciphertext"); + goto done; + } + + if (botan_pk_op_decrypt_create(&decrypt_op, b_key, hash_name, 0) != 0) { + goto done; + } + + if (botan_pk_op_decrypt(decrypt_op, out, out_len, in->m.mpi, in_len - 1) == 0) { + ret = RNP_SUCCESS; + } +done: + botan_privkey_destroy(b_key); + botan_pk_op_decrypt_destroy(decrypt_op); + return ret; +} diff --git a/src/lib/crypto/sm2.h b/src/lib/crypto/sm2.h new file mode 100644 index 0000000..a16f7fa --- /dev/null +++ b/src/lib/crypto/sm2.h @@ -0,0 +1,79 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_SM2_H_ +#define RNP_SM2_H_ + +#include "config.h" +#include "ec.h" + +typedef struct pgp_sm2_encrypted_t { + pgp_mpi_t m; +} pgp_sm2_encrypted_t; + +namespace rnp { +class Hash; +} // namespace rnp + +#if defined(ENABLE_SM2) +rnp_result_t sm2_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret); + +/** + * Compute the SM2 "ZA" field, and add it to the hash object + * + * If ident_field is null, uses the default value + */ +rnp_result_t sm2_compute_za(const pgp_ec_key_t &key, + rnp::Hash & hash, + const char * ident_field = NULL); + +rnp_result_t sm2_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key); + +rnp_result_t sm2_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key); + +rnp_result_t sm2_encrypt(rnp::RNG * rng, + pgp_sm2_encrypted_t *out, + const uint8_t * in, + size_t in_len, + pgp_hash_alg_t hash_algo, + const pgp_ec_key_t * key); + +rnp_result_t sm2_decrypt(uint8_t * out, + size_t * out_len, + const pgp_sm2_encrypted_t *in, + const pgp_ec_key_t * key); +#endif // defined(ENABLE_SM2) + +#endif // SM2_H_ diff --git a/src/lib/crypto/sm2_ossl.cpp b/src/lib/crypto/sm2_ossl.cpp new file mode 100644 index 0000000..4c1eaf1 --- /dev/null +++ b/src/lib/crypto/sm2_ossl.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include "sm2.h" +#include "utils.h" + +rnp_result_t +sm2_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + return RNP_ERROR_NOT_IMPLEMENTED; +} + +rnp_result_t +sm2_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + return RNP_ERROR_NOT_IMPLEMENTED; +} + +rnp_result_t +sm2_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + return RNP_ERROR_NOT_IMPLEMENTED; +} + +rnp_result_t +sm2_encrypt(rnp::RNG * rng, + pgp_sm2_encrypted_t *out, + const uint8_t * in, + size_t in_len, + pgp_hash_alg_t hash_algo, + const pgp_ec_key_t * key) +{ + return RNP_ERROR_NOT_IMPLEMENTED; +} + +rnp_result_t +sm2_decrypt(uint8_t * out, + size_t * out_len, + const pgp_sm2_encrypted_t *in, + const pgp_ec_key_t * key) +{ + return RNP_ERROR_NOT_IMPLEMENTED; +} diff --git a/src/lib/crypto/symmetric.cpp b/src/lib/crypto/symmetric.cpp new file mode 100644 index 0000000..aeed784 --- /dev/null +++ b/src/lib/crypto/symmetric.cpp @@ -0,0 +1,648 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "crypto.h" +#include "config.h" +#include "defaults.h" + +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <botan/ffi.h> +#include "utils.h" + +static const char * +pgp_sa_to_botan_string(int alg, bool silent = false) +{ + switch (alg) { +#if defined(BOTAN_HAS_IDEA) && defined(ENABLE_IDEA) + case PGP_SA_IDEA: + return "IDEA"; +#endif + +#if defined(BOTAN_HAS_DES) + case PGP_SA_TRIPLEDES: + return "TripleDES"; +#endif + +#if defined(BOTAN_HAS_CAST) && defined(ENABLE_CAST5) + case PGP_SA_CAST5: + return "CAST-128"; +#endif + +#if defined(BOTAN_HAS_BLOWFISH) && defined(ENABLE_BLOWFISH) + case PGP_SA_BLOWFISH: + return "Blowfish"; +#endif + +#if defined(BOTAN_HAS_AES) + case PGP_SA_AES_128: + return "AES-128"; + case PGP_SA_AES_192: + return "AES-192"; + case PGP_SA_AES_256: + return "AES-256"; +#endif + +#if defined(BOTAN_HAS_SM4) && defined(ENABLE_SM2) + case PGP_SA_SM4: + return "SM4"; +#endif + +#if defined(BOTAN_HAS_TWOFISH) && defined(ENABLE_TWOFISH) + case PGP_SA_TWOFISH: + return "Twofish"; +#endif + +#if defined(BOTAN_HAS_CAMELLIA) + case PGP_SA_CAMELLIA_128: + return "Camellia-128"; + case PGP_SA_CAMELLIA_192: + return "Camellia-192"; + case PGP_SA_CAMELLIA_256: + return "Camellia-256"; +#endif + + default: + if (!silent) { + RNP_LOG("Unsupported symmetric algorithm %d", alg); + } + return NULL; + } +} + +#if defined(ENABLE_AEAD) +static bool +pgp_aead_to_botan_string(pgp_symm_alg_t ealg, pgp_aead_alg_t aalg, char *buf, size_t len) +{ + const char *ealg_name = pgp_sa_to_botan_string(ealg); + size_t ealg_len; + + if (!ealg_name) { + return false; + } + + ealg_len = strlen(ealg_name); + + if (len < ealg_len + 5) { + RNP_LOG("buffer too small"); + return false; + } + + switch (aalg) { + case PGP_AEAD_EAX: + memcpy(buf, ealg_name, ealg_len); + strncpy(buf + ealg_len, "/EAX", len - ealg_len); + break; + case PGP_AEAD_OCB: + memcpy(buf, ealg_name, ealg_len); + strncpy(buf + ealg_len, "/OCB", len - ealg_len); + break; + default: + RNP_LOG("unsupported AEAD alg %d", (int) aalg); + return false; + } + + return true; +} +#endif + +bool +pgp_cipher_cfb_start(pgp_crypt_t * crypt, + pgp_symm_alg_t alg, + const uint8_t *key, + const uint8_t *iv) +{ + memset(crypt, 0x0, sizeof(*crypt)); + + const char *cipher_name = pgp_sa_to_botan_string(alg); + if (!cipher_name) { + return false; + } + + crypt->alg = alg; + crypt->blocksize = pgp_block_size(alg); + + // This shouldn't happen if pgp_sa_to_botan_string returned a ptr + if (botan_block_cipher_init(&(crypt->cfb.obj), cipher_name) != 0) { + RNP_LOG("Block cipher '%s' not available", cipher_name); + return false; + } + + const size_t keysize = pgp_key_size(alg); + + if (botan_block_cipher_set_key(crypt->cfb.obj, key, keysize) != 0) { + RNP_LOG("Failure setting key on block cipher object"); + return false; + } + + if (iv != NULL) { + // Otherwise left as all zeros via memset at start of function + memcpy(crypt->cfb.iv, iv, crypt->blocksize); + } + + crypt->cfb.remaining = 0; + + return true; +} + +void +pgp_cipher_cfb_resync(pgp_crypt_t *crypt, const uint8_t *buf) +{ + /* iv will be encrypted in the upcoming call to encrypt/decrypt */ + memcpy(crypt->cfb.iv, buf, crypt->blocksize); + crypt->cfb.remaining = 0; +} + +int +pgp_cipher_cfb_finish(pgp_crypt_t *crypt) +{ + if (!crypt) { + return 0; + } + if (crypt->cfb.obj) { + botan_block_cipher_destroy(crypt->cfb.obj); + crypt->cfb.obj = NULL; + } + botan_scrub_mem((uint8_t *) crypt, sizeof(*crypt)); + return 0; +} + +/* we rely on fact that in and out could be the same */ +int +pgp_cipher_cfb_encrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes) +{ + uint64_t *in64; + uint64_t buf64[512]; // 4KB - page size + uint64_t iv64[2]; + size_t blocks, blockb; + unsigned blsize = crypt->blocksize; + + /* encrypting till the block boundary */ + while (bytes && crypt->cfb.remaining) { + *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++; + crypt->cfb.remaining--; + bytes--; + } + + if (!bytes) { + return 0; + } + + /* encrypting full blocks */ + if (bytes > blsize) { + memcpy(iv64, crypt->cfb.iv, blsize); + while ((blocks = bytes & ~(blsize - 1)) > 0) { + if (blocks > sizeof(buf64)) { + blocks = sizeof(buf64); + } + bytes -= blocks; + blockb = blocks; + memcpy(buf64, in, blockb); + in64 = buf64; + + if (blsize == 16) { + blocks >>= 4; + while (blocks--) { + botan_block_cipher_encrypt_blocks( + crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1); + *in64 ^= iv64[0]; + iv64[0] = *in64++; + *in64 ^= iv64[1]; + iv64[1] = *in64++; + } + } else { + blocks >>= 3; + while (blocks--) { + botan_block_cipher_encrypt_blocks( + crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1); + *in64 ^= iv64[0]; + iv64[0] = *in64++; + } + } + + memcpy(out, buf64, blockb); + out += blockb; + in += blockb; + } + + memcpy(crypt->cfb.iv, iv64, blsize); + } + + if (!bytes) { + return 0; + } + + botan_block_cipher_encrypt_blocks(crypt->cfb.obj, crypt->cfb.iv, crypt->cfb.iv, 1); + crypt->cfb.remaining = blsize; + + /* encrypting tail */ + while (bytes) { + *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++; + crypt->cfb.remaining--; + bytes--; + } + + return 0; +} + +/* we rely on fact that in and out could be the same */ +int +pgp_cipher_cfb_decrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes) +{ + /* for better code readability */ + uint64_t *out64, *in64; + uint64_t inbuf64[512]; // 4KB - page size + uint64_t outbuf64[512]; + uint64_t iv64[2]; + size_t blocks, blockb; + unsigned blsize = crypt->blocksize; + + /* decrypting till the block boundary */ + while (bytes && crypt->cfb.remaining) { + uint8_t c = *in++; + *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = c; + crypt->cfb.remaining--; + bytes--; + } + + if (!bytes) { + return 0; + } + + /* decrypting full blocks */ + if (bytes > blsize) { + memcpy(iv64, crypt->cfb.iv, blsize); + + while ((blocks = bytes & ~(blsize - 1)) > 0) { + if (blocks > sizeof(inbuf64)) { + blocks = sizeof(inbuf64); + } + bytes -= blocks; + blockb = blocks; + memcpy(inbuf64, in, blockb); + out64 = outbuf64; + in64 = inbuf64; + + if (blsize == 16) { + blocks >>= 4; + while (blocks--) { + botan_block_cipher_encrypt_blocks( + crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1); + *out64++ = *in64 ^ iv64[0]; + iv64[0] = *in64++; + *out64++ = *in64 ^ iv64[1]; + iv64[1] = *in64++; + } + } else { + blocks >>= 3; + while (blocks--) { + botan_block_cipher_encrypt_blocks( + crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1); + *out64++ = *in64 ^ iv64[0]; + iv64[0] = *in64++; + } + } + + memcpy(out, outbuf64, blockb); + out += blockb; + in += blockb; + } + + memcpy(crypt->cfb.iv, iv64, blsize); + } + + if (!bytes) { + return 0; + } + + botan_block_cipher_encrypt_blocks(crypt->cfb.obj, crypt->cfb.iv, crypt->cfb.iv, 1); + crypt->cfb.remaining = blsize; + + /* decrypting tail */ + while (bytes) { + uint8_t c = *in++; + *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = c; + crypt->cfb.remaining--; + bytes--; + } + + return 0; +} + +size_t +pgp_cipher_block_size(pgp_crypt_t *crypt) +{ + return crypt->blocksize; +} + +unsigned +pgp_block_size(pgp_symm_alg_t alg) +{ + switch (alg) { + case PGP_SA_IDEA: + case PGP_SA_TRIPLEDES: + case PGP_SA_CAST5: + case PGP_SA_BLOWFISH: + return 8; + case PGP_SA_AES_128: + case PGP_SA_AES_192: + case PGP_SA_AES_256: + case PGP_SA_TWOFISH: + case PGP_SA_CAMELLIA_128: + case PGP_SA_CAMELLIA_192: + case PGP_SA_CAMELLIA_256: + case PGP_SA_SM4: + return 16; + default: + return 0; + } +} + +unsigned +pgp_key_size(pgp_symm_alg_t alg) +{ + /* Update MAX_SYMM_KEY_SIZE after adding algorithm + * with bigger key size. + */ + static_assert(32 == MAX_SYMM_KEY_SIZE, "MAX_SYMM_KEY_SIZE must be updated"); + + switch (alg) { + case PGP_SA_IDEA: + case PGP_SA_CAST5: + case PGP_SA_BLOWFISH: + case PGP_SA_AES_128: + case PGP_SA_CAMELLIA_128: + case PGP_SA_SM4: + return 16; + + case PGP_SA_TRIPLEDES: + case PGP_SA_AES_192: + case PGP_SA_CAMELLIA_192: + return 24; + + case PGP_SA_TWOFISH: + case PGP_SA_AES_256: + case PGP_SA_CAMELLIA_256: + return 32; + + default: + return 0; + } +} + +bool +pgp_is_sa_supported(int alg, bool silent) +{ + return pgp_sa_to_botan_string(alg, silent); +} + +#if defined(ENABLE_AEAD) +bool +pgp_cipher_aead_init(pgp_crypt_t * crypt, + pgp_symm_alg_t ealg, + pgp_aead_alg_t aalg, + const uint8_t *key, + bool decrypt) +{ + char cipher_name[32]; + uint32_t flags; + + memset(crypt, 0x0, sizeof(*crypt)); + + if (!pgp_aead_to_botan_string(ealg, aalg, cipher_name, sizeof(cipher_name))) { + return false; + } + + crypt->alg = ealg; + crypt->blocksize = pgp_block_size(ealg); + crypt->aead.alg = aalg; + crypt->aead.decrypt = decrypt; + crypt->aead.taglen = PGP_AEAD_EAX_OCB_TAG_LEN; /* it's the same for EAX and OCB */ + + flags = decrypt ? BOTAN_CIPHER_INIT_FLAG_DECRYPT : BOTAN_CIPHER_INIT_FLAG_ENCRYPT; + + if (botan_cipher_init(&(crypt->aead.obj), cipher_name, flags)) { + RNP_LOG("cipher %s is not available", cipher_name); + return false; + } + + if (botan_cipher_set_key(crypt->aead.obj, key, (size_t) pgp_key_size(ealg))) { + RNP_LOG("failed to set key"); + return false; + } + + if (botan_cipher_get_update_granularity(crypt->aead.obj, &crypt->aead.granularity)) { + RNP_LOG("failed to get update granularity"); + return false; + } + + return true; +} + +size_t +pgp_cipher_aead_granularity(pgp_crypt_t *crypt) +{ + return crypt->aead.granularity; +} +#endif + +size_t +pgp_cipher_aead_nonce_len(pgp_aead_alg_t aalg) +{ + switch (aalg) { + case PGP_AEAD_EAX: + return PGP_AEAD_EAX_NONCE_LEN; + case PGP_AEAD_OCB: + return PGP_AEAD_OCB_NONCE_LEN; + default: + return 0; + } +} + +size_t +pgp_cipher_aead_tag_len(pgp_aead_alg_t aalg) +{ + switch (aalg) { + case PGP_AEAD_EAX: + case PGP_AEAD_OCB: + return PGP_AEAD_EAX_OCB_TAG_LEN; + default: + return 0; + } +} + +#if defined(ENABLE_AEAD) +bool +pgp_cipher_aead_set_ad(pgp_crypt_t *crypt, const uint8_t *ad, size_t len) +{ + return botan_cipher_set_associated_data(crypt->aead.obj, ad, len) == 0; +} + +bool +pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len) +{ + return botan_cipher_start(crypt->aead.obj, nonce, len) == 0; +} + +bool +pgp_cipher_aead_update(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len) +{ + size_t outwr = 0; + size_t inread = 0; + + if (len % crypt->aead.granularity) { + RNP_LOG("aead wrong update len"); + return false; + } + + if (botan_cipher_update(crypt->aead.obj, 0, out, len, &outwr, in, len, &inread) != 0) { + RNP_LOG("aead update failed"); + return false; + } + + if ((outwr != len) || (inread != len)) { + RNP_LOG("wrong aead usage"); + return false; + } + + return true; +} + +void +pgp_cipher_aead_reset(pgp_crypt_t *crypt) +{ + botan_cipher_reset(crypt->aead.obj); +} + +bool +pgp_cipher_aead_finish(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len) +{ + uint32_t flags = BOTAN_CIPHER_UPDATE_FLAG_FINAL; + size_t inread = 0; + size_t outwr = 0; + int res; + + if (crypt->aead.decrypt) { + size_t datalen = len - crypt->aead.taglen; + /* for decryption we should have tag for the final update call */ + res = + botan_cipher_update(crypt->aead.obj, flags, out, datalen, &outwr, in, len, &inread); + if (res != 0) { + if (res != BOTAN_FFI_ERROR_BAD_MAC) { + RNP_LOG("aead finish failed: %d", res); + } + return false; + } + + if ((outwr != datalen) || (inread != len)) { + RNP_LOG("wrong decrypt aead finish usage"); + return false; + } + } else { + /* for encryption tag will be generated */ + size_t outlen = len + crypt->aead.taglen; + if (botan_cipher_update( + crypt->aead.obj, flags, out, outlen, &outwr, in, len, &inread) != 0) { + RNP_LOG("aead finish failed"); + return false; + } + + if ((outwr != outlen) || (inread != len)) { + RNP_LOG("wrong encrypt aead finish usage"); + return false; + } + } + + pgp_cipher_aead_reset(crypt); + return true; +} + +void +pgp_cipher_aead_destroy(pgp_crypt_t *crypt) +{ + botan_cipher_destroy(crypt->aead.obj); +} + +size_t +pgp_cipher_aead_nonce(pgp_aead_alg_t aalg, const uint8_t *iv, uint8_t *nonce, size_t index) +{ + switch (aalg) { + case PGP_AEAD_EAX: + /* The nonce for EAX mode is computed by treating the starting + initialization vector as a 16-octet, big-endian value and + exclusive-oring the low eight octets of it with the chunk index. + */ + memcpy(nonce, iv, PGP_AEAD_EAX_NONCE_LEN); + for (int i = 15; (i > 7) && index; i--) { + nonce[i] ^= index & 0xff; + index = index >> 8; + } + return PGP_AEAD_EAX_NONCE_LEN; + case PGP_AEAD_OCB: + /* The nonce for a chunk of chunk index "i" in OCB processing is defined as: + OCB-Nonce_{i} = IV[1..120] xor i + */ + memcpy(nonce, iv, PGP_AEAD_OCB_NONCE_LEN); + for (int i = 14; (i >= 0) && index; i--) { + nonce[i] ^= index & 0xff; + index = index >> 8; + } + return PGP_AEAD_OCB_NONCE_LEN; + default: + return 0; + } +} +#endif // ENABLE_AEAD diff --git a/src/lib/crypto/symmetric.h b/src/lib/crypto/symmetric.h new file mode 100644 index 0000000..a50fe9a --- /dev/null +++ b/src/lib/crypto/symmetric.h @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2017, 2021 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYMMETRIC_CRYPTO_H_ +#define SYMMETRIC_CRYPTO_H_ + +#include <repgp/repgp_def.h> +#include "crypto/rng.h" +#include "config.h" +#ifdef CRYPTO_BACKEND_OPENSSL +#include <openssl/evp.h> +#include "mem.h" +#endif + +/* Nonce len for AEAD/EAX */ +#define PGP_AEAD_EAX_NONCE_LEN 16 + +/* Nonce len for AEAD/OCB */ +#define PGP_AEAD_OCB_NONCE_LEN 15 + +/* Maximum AEAD nonce length */ +#define PGP_AEAD_MAX_NONCE_LEN 16 + +/* Authentication tag len for AEAD/EAX and AEAD/OCB */ +#define PGP_AEAD_EAX_OCB_TAG_LEN 16 + +/* Maximal size of symmetric key */ +#define MAX_SYMM_KEY_SIZE 32 + +/* Maximum AEAD tag length */ +#define PGP_AEAD_MAX_TAG_LEN 16 + +/* Maximum authenticated data length for AEAD */ +#define PGP_AEAD_MAX_AD_LEN 32 + +struct pgp_crypt_cfb_param_t { +#ifdef CRYPTO_BACKEND_BOTAN + struct botan_block_cipher_struct *obj; +#endif +#ifdef CRYPTO_BACKEND_OPENSSL + EVP_CIPHER_CTX *obj; +#endif + size_t remaining; + uint8_t iv[PGP_MAX_BLOCK_SIZE]; +}; + +struct pgp_crypt_aead_param_t { +#ifdef CRYPTO_BACKEND_BOTAN + struct botan_cipher_struct *obj; +#endif +#ifdef CRYPTO_BACKEND_OPENSSL + EVP_CIPHER_CTX * obj; + const EVP_CIPHER * cipher; + rnp::secure_vector<uint8_t> *key; + uint8_t ad[PGP_AEAD_MAX_AD_LEN]; + size_t ad_len; + size_t n_len; +#endif + pgp_aead_alg_t alg; + bool decrypt; + size_t granularity; + size_t taglen; +}; + +/** pgp_crypt_t */ +typedef struct pgp_crypt_t { + union { + struct pgp_crypt_cfb_param_t cfb; +#if defined(ENABLE_AEAD) + struct pgp_crypt_aead_param_t aead; +#endif + }; + + pgp_symm_alg_t alg; + size_t blocksize; + rnp::RNG * rng; +} pgp_crypt_t; + +unsigned pgp_block_size(pgp_symm_alg_t); +unsigned pgp_key_size(pgp_symm_alg_t); +bool pgp_is_sa_supported(int alg, bool silent = false); +size_t pgp_cipher_block_size(pgp_crypt_t *crypt); + +/** + * Initialize a cipher object. + * @param iv if null an all-zero IV is assumed + */ +bool pgp_cipher_cfb_start(pgp_crypt_t * crypt, + pgp_symm_alg_t alg, + const uint8_t *key, + const uint8_t *iv); + +// Deallocate all storage +int pgp_cipher_cfb_finish(pgp_crypt_t *crypt); +// CFB encryption/decryption +int pgp_cipher_cfb_encrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len); +int pgp_cipher_cfb_decrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len); + +void pgp_cipher_cfb_resync(pgp_crypt_t *crypt, const uint8_t *buf); + +#if defined(ENABLE_AEAD) +/** @brief Initialize AEAD cipher instance + * @param crypt pgp crypto object + * @param ealg symmetric encryption algorithm to use together with AEAD cipher mode + * @param aalg AEAD cipher mode. Only EAX is supported now + * @param key key buffer. Number of key bytes is determined by ealg. + * @param decrypt true for decryption, or false for encryption + * @return true on success or false otherwise. + */ +bool pgp_cipher_aead_init(pgp_crypt_t * crypt, + pgp_symm_alg_t ealg, + pgp_aead_alg_t aalg, + const uint8_t *key, + bool decrypt); + +/** @brief Return the AEAD cipher update granularity. Botan FFI will consume chunks which are + * multiple of this value. See the description of pgp_cipher_aead_update() + * @param crypt initialized AEAD crypto + * @return Update granularity value in bytes + */ +size_t pgp_cipher_aead_granularity(pgp_crypt_t *crypt); +#endif + +/** @brief Return the AEAD cipher tag length + * @param aalg OpenPGP AEAD algorithm + * @return length of authentication tag in bytes, or 0 for unknown algorithm + */ +size_t pgp_cipher_aead_tag_len(pgp_aead_alg_t aalg); + +/** @brief Return the AEAD cipher nonce and IV length + * @param aalg OpenPGP AEAD algorithm + * @return length of nonce in bytes, or 0 for unknown algorithm + */ +size_t pgp_cipher_aead_nonce_len(pgp_aead_alg_t aalg); + +#if defined(ENABLE_AEAD) +/** @brief Set associated data + * @param crypt initialized AEAD crypto + * @param ad buffer with data. Cannot be NULL. + * @param len number of bytes in ad + * @return true on success or false otherwise. + */ +bool pgp_cipher_aead_set_ad(pgp_crypt_t *crypt, const uint8_t *ad, size_t len); + +/** @brief Start the cipher operation, using the given nonce + * @param crypt initialized AEAD crypto + * @param nonce buffer with nonce, cannot be NULL. + * @param len number of bytes in nonce. Must conform to the cipher properties. + * @return true on success or false otherwise. + */ +bool pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len); + +/** @brief Update the cipher. This should be called for non-final data, respecting the + * update granularity of underlying botan cipher. Now it is 256 bytes. + * @param crypt initialized AEAD crypto + * @param out buffer to put processed data. Cannot be NULL, and should be large enough to put + * len bytes + * @param in buffer with input, cannot be NULL + * @param len number of bytes to process. Should be multiple of update granularity. + * @return true on success or false otherwise. On success exactly len processed bytes will be + * stored in out buffer + */ +bool pgp_cipher_aead_update(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len); + +/** @brief Do final update on the cipher. For decryption final chunk should contain at least + * authentication tag, for encryption input could be zero-size. + * @param crypt initialized AEAD crypto + * @param out buffer to put processed data. For decryption it should be large enough to put + * len bytes minus authentication tag, for encryption it should be large enough to + * put len byts plus a tag. + * @param in buffer with input, if any. May be NULL for encryption, then len should be zero. + * For decryption it should contain at least authentication tag. + * @param len number of input bytes bytes + * @return true on success or false otherwise. On success for decryption len minus tag size + * bytes will be stored in out, for encryption out will contain len bytes plus + * tag size. + */ +bool pgp_cipher_aead_finish(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len); + +/** @brief Reset the AEAD cipher's state, calling the finish() and ignoring the result + * @param crypt initialized AEAD crypto + */ +void pgp_cipher_aead_reset(pgp_crypt_t *crypt); + +/** @brief Destroy the cipher object, deallocating all the memory. + * @param crypt initialized AEAD crypto + */ +void pgp_cipher_aead_destroy(pgp_crypt_t *crypt); + +/** @brief Helper function to set AEAD nonce for the chunk by its index. + * iv and nonce should be large enough to hold max nonce bytes + * @param aalg AEAD algorithm used + * @param iv Initial vector for the message, must have 16 bytes of data + * @param nonce Nonce to fill up, should have space for 16 bytes of data + * @param index Chunk's index + * @return Length of the nonce, or 0 if algorithm is unknown + */ +size_t pgp_cipher_aead_nonce(pgp_aead_alg_t aalg, + const uint8_t *iv, + uint8_t * nonce, + size_t index); +#endif // ENABLE_AEAD + +#endif diff --git a/src/lib/crypto/symmetric_ossl.cpp b/src/lib/crypto/symmetric_ossl.cpp new file mode 100644 index 0000000..98e90ed --- /dev/null +++ b/src/lib/crypto/symmetric_ossl.cpp @@ -0,0 +1,644 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "crypto.h" +#include "config.h" +#include "defaults.h" + +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <openssl/evp.h> +#include <openssl/err.h> +#include "mem.h" +#include "utils.h" + +static const char * +pgp_sa_to_openssl_string(int alg, bool silent = false) +{ + switch (alg) { +#if defined(ENABLE_IDEA) + case PGP_SA_IDEA: + return "idea-ecb"; +#endif + case PGP_SA_TRIPLEDES: + return "des-ede3"; +#if defined(ENABLE_CAST5) + case PGP_SA_CAST5: + return "cast5-ecb"; +#endif +#if defined(ENABLE_BLOWFISH) + case PGP_SA_BLOWFISH: + return "bf-ecb"; +#endif + case PGP_SA_AES_128: + return "aes-128-ecb"; + case PGP_SA_AES_192: + return "aes-192-ecb"; + case PGP_SA_AES_256: + return "aes-256-ecb"; +#if defined(ENABLE_SM2) + case PGP_SA_SM4: + return "sm4-ecb"; +#endif + case PGP_SA_CAMELLIA_128: + return "camellia-128-ecb"; + case PGP_SA_CAMELLIA_192: + return "camellia-192-ecb"; + case PGP_SA_CAMELLIA_256: + return "camellia-256-ecb"; + default: + if (!silent) { + RNP_LOG("Unsupported symmetric algorithm %d", alg); + } + return NULL; + } +} + +bool +pgp_cipher_cfb_start(pgp_crypt_t * crypt, + pgp_symm_alg_t alg, + const uint8_t *key, + const uint8_t *iv) +{ + memset(crypt, 0x0, sizeof(*crypt)); + + const char *cipher_name = pgp_sa_to_openssl_string(alg); + if (!cipher_name) { + RNP_LOG("Unsupported algorithm: %d", alg); + return false; + } + + const EVP_CIPHER *cipher = EVP_get_cipherbyname(cipher_name); + if (!cipher) { + RNP_LOG("Cipher %s is not supported by OpenSSL.", cipher_name); + return false; + } + + crypt->alg = alg; + crypt->blocksize = pgp_block_size(alg); + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int res = EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv); + if (res != 1) { + RNP_LOG("Failed to initialize cipher."); + EVP_CIPHER_CTX_free(ctx); + return false; + } + crypt->cfb.obj = ctx; + + if (iv) { + // Otherwise left as all zeros via memset at start of function + memcpy(crypt->cfb.iv, iv, crypt->blocksize); + } + + crypt->cfb.remaining = 0; + return true; +} + +void +pgp_cipher_cfb_resync(pgp_crypt_t *crypt, const uint8_t *buf) +{ + /* iv will be encrypted in the upcoming call to encrypt/decrypt */ + memcpy(crypt->cfb.iv, buf, crypt->blocksize); + crypt->cfb.remaining = 0; +} + +int +pgp_cipher_cfb_finish(pgp_crypt_t *crypt) +{ + if (!crypt) { + return 0; + } + if (crypt->cfb.obj) { + EVP_CIPHER_CTX_free(crypt->cfb.obj); + crypt->cfb.obj = NULL; + } + OPENSSL_cleanse((uint8_t *) crypt, sizeof(*crypt)); + return 0; +} + +/* we rely on fact that in and out could be the same */ +int +pgp_cipher_cfb_encrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes) +{ + uint64_t *in64; + uint64_t buf64[512]; // 4KB - page size + uint64_t iv64[2]; + size_t blocks, blockb; + unsigned blsize = crypt->blocksize; + + /* encrypting till the block boundary */ + while (bytes && crypt->cfb.remaining) { + *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++; + crypt->cfb.remaining--; + bytes--; + } + + if (!bytes) { + return 0; + } + + /* encrypting full blocks */ + if (bytes > blsize) { + memcpy(iv64, crypt->cfb.iv, blsize); + while ((blocks = bytes & ~(blsize - 1)) > 0) { + if (blocks > sizeof(buf64)) { + blocks = sizeof(buf64); + } + bytes -= blocks; + blockb = blocks; + memcpy(buf64, in, blockb); + in64 = buf64; + + if (blsize == 16) { + blocks >>= 4; + while (blocks--) { + int outlen = 16; + EVP_EncryptUpdate( + crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 16); + if (outlen != 16) { + RNP_LOG("Bad outlen: must be 16"); + } + *in64 ^= iv64[0]; + iv64[0] = *in64++; + *in64 ^= iv64[1]; + iv64[1] = *in64++; + } + } else { + blocks >>= 3; + while (blocks--) { + int outlen = 8; + EVP_EncryptUpdate( + crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 8); + if (outlen != 8) { + RNP_LOG("Bad outlen: must be 8"); + } + *in64 ^= iv64[0]; + iv64[0] = *in64++; + } + } + + memcpy(out, buf64, blockb); + out += blockb; + in += blockb; + } + + memcpy(crypt->cfb.iv, iv64, blsize); + } + + if (!bytes) { + return 0; + } + + int outlen = blsize; + EVP_EncryptUpdate(crypt->cfb.obj, crypt->cfb.iv, &outlen, crypt->cfb.iv, (int) blsize); + if (outlen != (int) blsize) { + RNP_LOG("Bad outlen: must be %u", blsize); + } + crypt->cfb.remaining = blsize; + + /* encrypting tail */ + while (bytes) { + *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++; + crypt->cfb.remaining--; + bytes--; + } + + return 0; +} + +/* we rely on fact that in and out could be the same */ +int +pgp_cipher_cfb_decrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes) +{ + /* for better code readability */ + uint64_t *out64, *in64; + uint64_t inbuf64[512]; // 4KB - page size + uint64_t outbuf64[512]; + uint64_t iv64[2]; + size_t blocks, blockb; + unsigned blsize = crypt->blocksize; + + /* decrypting till the block boundary */ + while (bytes && crypt->cfb.remaining) { + uint8_t c = *in++; + *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = c; + crypt->cfb.remaining--; + bytes--; + } + + if (!bytes) { + return 0; + } + + /* decrypting full blocks */ + if (bytes > blsize) { + memcpy(iv64, crypt->cfb.iv, blsize); + + while ((blocks = bytes & ~(blsize - 1)) > 0) { + if (blocks > sizeof(inbuf64)) { + blocks = sizeof(inbuf64); + } + bytes -= blocks; + blockb = blocks; + memcpy(inbuf64, in, blockb); + out64 = outbuf64; + in64 = inbuf64; + + if (blsize == 16) { + blocks >>= 4; + while (blocks--) { + int outlen = 16; + EVP_EncryptUpdate( + crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 16); + if (outlen != 16) { + RNP_LOG("Bad outlen: must be 16"); + } + *out64++ = *in64 ^ iv64[0]; + iv64[0] = *in64++; + *out64++ = *in64 ^ iv64[1]; + iv64[1] = *in64++; + } + } else { + blocks >>= 3; + while (blocks--) { + int outlen = 8; + EVP_EncryptUpdate( + crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 8); + if (outlen != 8) { + RNP_LOG("Bad outlen: must be 8"); + } + *out64++ = *in64 ^ iv64[0]; + iv64[0] = *in64++; + } + } + + memcpy(out, outbuf64, blockb); + out += blockb; + in += blockb; + } + + memcpy(crypt->cfb.iv, iv64, blsize); + } + + if (!bytes) { + return 0; + } + + int outlen = blsize; + EVP_EncryptUpdate(crypt->cfb.obj, crypt->cfb.iv, &outlen, crypt->cfb.iv, (int) blsize); + if (outlen != (int) blsize) { + RNP_LOG("Bad outlen: must be %u", blsize); + } + crypt->cfb.remaining = blsize; + + /* decrypting tail */ + while (bytes) { + uint8_t c = *in++; + *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = c; + crypt->cfb.remaining--; + bytes--; + } + + return 0; +} + +size_t +pgp_cipher_block_size(pgp_crypt_t *crypt) +{ + return crypt->blocksize; +} + +unsigned +pgp_block_size(pgp_symm_alg_t alg) +{ + switch (alg) { + case PGP_SA_IDEA: + case PGP_SA_TRIPLEDES: + case PGP_SA_CAST5: + case PGP_SA_BLOWFISH: + return 8; + case PGP_SA_AES_128: + case PGP_SA_AES_192: + case PGP_SA_AES_256: + case PGP_SA_TWOFISH: + case PGP_SA_CAMELLIA_128: + case PGP_SA_CAMELLIA_192: + case PGP_SA_CAMELLIA_256: + case PGP_SA_SM4: + return 16; + default: + return 0; + } +} + +unsigned +pgp_key_size(pgp_symm_alg_t alg) +{ + /* Update MAX_SYMM_KEY_SIZE after adding algorithm + * with bigger key size. + */ + static_assert(32 == MAX_SYMM_KEY_SIZE, "MAX_SYMM_KEY_SIZE must be updated"); + + switch (alg) { + case PGP_SA_IDEA: + case PGP_SA_CAST5: + case PGP_SA_BLOWFISH: + case PGP_SA_AES_128: + case PGP_SA_CAMELLIA_128: + case PGP_SA_SM4: + return 16; + case PGP_SA_TRIPLEDES: + case PGP_SA_AES_192: + case PGP_SA_CAMELLIA_192: + return 24; + case PGP_SA_TWOFISH: + case PGP_SA_AES_256: + case PGP_SA_CAMELLIA_256: + return 32; + default: + return 0; + } +} + +bool +pgp_is_sa_supported(int alg, bool silent) +{ + return pgp_sa_to_openssl_string(alg, silent); +} + +#if defined(ENABLE_AEAD) + +static const char * +openssl_aead_name(pgp_symm_alg_t ealg, pgp_aead_alg_t aalg) +{ + switch (aalg) { + case PGP_AEAD_OCB: + break; + default: + RNP_LOG("Only OCB mode is supported by the OpenSSL backend."); + return NULL; + } + switch (ealg) { + case PGP_SA_AES_128: + return "AES-128-OCB"; + case PGP_SA_AES_192: + return "AES-192-OCB"; + case PGP_SA_AES_256: + return "AES-256-OCB"; + default: + RNP_LOG("Only AES-OCB is supported by the OpenSSL backend."); + return NULL; + } +} + +bool +pgp_cipher_aead_init(pgp_crypt_t * crypt, + pgp_symm_alg_t ealg, + pgp_aead_alg_t aalg, + const uint8_t *key, + bool decrypt) +{ + memset(crypt, 0x0, sizeof(*crypt)); + /* OpenSSL backend currently supports only AES-OCB */ + const char *algname = openssl_aead_name(ealg, aalg); + if (!algname) { + return false; + } + auto cipher = EVP_get_cipherbyname(algname); + if (!cipher) { + RNP_LOG("Cipher %s is not supported.", algname); + return false; + } + /* Create and setup context */ + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + RNP_LOG("Failed to create cipher context: %lu", ERR_peek_last_error()); + return false; + } + + crypt->aead.key = new rnp::secure_vector<uint8_t>(key, key + pgp_key_size(ealg)); + crypt->alg = ealg; + crypt->blocksize = pgp_block_size(ealg); + crypt->aead.cipher = cipher; + crypt->aead.obj = ctx; + crypt->aead.alg = aalg; + crypt->aead.decrypt = decrypt; + crypt->aead.granularity = crypt->blocksize; + crypt->aead.taglen = PGP_AEAD_EAX_OCB_TAG_LEN; + crypt->aead.ad_len = 0; + crypt->aead.n_len = pgp_cipher_aead_nonce_len(aalg); + return true; +} + +size_t +pgp_cipher_aead_granularity(pgp_crypt_t *crypt) +{ + return crypt->aead.granularity; +} +#endif + +size_t +pgp_cipher_aead_nonce_len(pgp_aead_alg_t aalg) +{ + switch (aalg) { + case PGP_AEAD_EAX: + return PGP_AEAD_EAX_NONCE_LEN; + case PGP_AEAD_OCB: + return PGP_AEAD_OCB_NONCE_LEN; + default: + return 0; + } +} + +size_t +pgp_cipher_aead_tag_len(pgp_aead_alg_t aalg) +{ + switch (aalg) { + case PGP_AEAD_EAX: + case PGP_AEAD_OCB: + return PGP_AEAD_EAX_OCB_TAG_LEN; + default: + return 0; + } +} + +#if defined(ENABLE_AEAD) +bool +pgp_cipher_aead_set_ad(pgp_crypt_t *crypt, const uint8_t *ad, size_t len) +{ + assert(len <= sizeof(crypt->aead.ad)); + memcpy(crypt->aead.ad, ad, len); + crypt->aead.ad_len = len; + return true; +} + +bool +pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len) +{ + auto &aead = crypt->aead; + auto ctx = aead.obj; + int enc = aead.decrypt ? 0 : 1; + assert(len == aead.n_len); + EVP_CIPHER_CTX_reset(ctx); + if (EVP_CipherInit_ex(ctx, aead.cipher, NULL, NULL, NULL, enc) != 1) { + RNP_LOG("Failed to initialize cipher: %lu", ERR_peek_last_error()); + return false; + } + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, aead.n_len, NULL) != 1) { + RNP_LOG("Failed to set nonce length: %lu", ERR_peek_last_error()); + return false; + } + if (EVP_CipherInit_ex(ctx, NULL, NULL, aead.key->data(), nonce, enc) != 1) { + RNP_LOG("Failed to start cipher: %lu", ERR_peek_last_error()); + return false; + } + int adlen = 0; + if (EVP_CipherUpdate(ctx, NULL, &adlen, aead.ad, aead.ad_len) != 1) { + RNP_LOG("Failed to set AD: %lu", ERR_peek_last_error()); + return false; + } + return true; +} + +bool +pgp_cipher_aead_update(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len) +{ + if (!len) { + return true; + } + int out_len = 0; + bool res = EVP_CipherUpdate(crypt->aead.obj, out, &out_len, in, len) == 1; + if (!res) { + RNP_LOG("Failed to update cipher: %lu", ERR_peek_last_error()); + } + assert(out_len == (int) len); + return res; +} + +void +pgp_cipher_aead_reset(pgp_crypt_t *crypt) +{ + /* Do nothing as subsequent pgp_cipher_aead_start() call will reset context */ +} + +bool +pgp_cipher_aead_finish(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len) +{ + auto &aead = crypt->aead; + auto ctx = aead.obj; + if (aead.decrypt) { + assert(len >= aead.taglen); + if (len < aead.taglen) { + RNP_LOG("Invalid state: too few input bytes."); + return false; + } + size_t data_len = len - aead.taglen; + int out_len = 0; + if (EVP_CipherUpdate(ctx, out, &out_len, in, data_len) != 1) { + RNP_LOG("Failed to update cipher: %lu", ERR_peek_last_error()); + return false; + } + uint8_t tag[PGP_AEAD_MAX_TAG_LEN] = {0}; + memcpy(tag, in + data_len, aead.taglen); + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, aead.taglen, tag) != 1) { + RNP_LOG("Failed to set tag: %lu", ERR_peek_last_error()); + return false; + } + int out_len2 = 0; + if (EVP_CipherFinal_ex(ctx, out + out_len, &out_len2) != 1) { + /* Zero value if auth tag is incorrect */ + if (ERR_peek_last_error()) { + RNP_LOG("Failed to finish AEAD decryption: %lu", ERR_peek_last_error()); + } + return false; + } + assert(out_len + out_len2 == (int) (len - aead.taglen)); + } else { + int out_len = 0; + if (EVP_CipherUpdate(ctx, out, &out_len, in, len) != 1) { + RNP_LOG("Failed to update cipher: %lu", ERR_peek_last_error()); + return false; + } + int out_len2 = 0; + if (EVP_CipherFinal_ex(ctx, out + out_len, &out_len2) != 1) { + RNP_LOG("Failed to finish AEAD encryption: %lu", ERR_peek_last_error()); + return false; + } + assert(out_len + out_len2 == (int) len); + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, aead.taglen, out + len) != 1) { + RNP_LOG("Failed to get tag: %lu", ERR_peek_last_error()); + return false; + } + } + return true; +} + +void +pgp_cipher_aead_destroy(pgp_crypt_t *crypt) +{ + if (crypt->aead.obj) { + EVP_CIPHER_CTX_free(crypt->aead.obj); + } + delete crypt->aead.key; + memset(crypt, 0x0, sizeof(*crypt)); +} + +size_t +pgp_cipher_aead_nonce(pgp_aead_alg_t aalg, const uint8_t *iv, uint8_t *nonce, size_t index) +{ + switch (aalg) { + case PGP_AEAD_EAX: + /* The nonce for EAX mode is computed by treating the starting + initialization vector as a 16-octet, big-endian value and + exclusive-oring the low eight octets of it with the chunk index. + */ + memcpy(nonce, iv, PGP_AEAD_EAX_NONCE_LEN); + for (int i = 15; (i > 7) && index; i--) { + nonce[i] ^= index & 0xff; + index = index >> 8; + } + return PGP_AEAD_EAX_NONCE_LEN; + case PGP_AEAD_OCB: + /* The nonce for a chunk of chunk index "i" in OCB processing is defined as: + OCB-Nonce_{i} = IV[1..120] xor i + */ + memcpy(nonce, iv, PGP_AEAD_OCB_NONCE_LEN); + for (int i = 14; (i >= 0) && index; i--) { + nonce[i] ^= index & 0xff; + index = index >> 8; + } + return PGP_AEAD_OCB_NONCE_LEN; + default: + return 0; + } +} +#endif diff --git a/src/lib/defaults.h b/src/lib/defaults.h new file mode 100644 index 0000000..22a3c46 --- /dev/null +++ b/src/lib/defaults.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018-2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DEFAULTS_H_ +#define DEFAULTS_H_ + +/* Default hash algorithm as PGP constant */ +#define DEFAULT_PGP_HASH_ALG PGP_HASH_SHA256 + +/* Default symmetric algorithm as PGP constant */ +#define DEFAULT_PGP_SYMM_ALG PGP_SA_AES_256 + +/* Default number of msec to run S2K derivation */ +#define DEFAULT_S2K_MSEC 150 + +/* Default number of msec to run S2K tuning */ +#define DEFAULT_S2K_TUNE_MSEC 10 + +/* Default compression algorithm and level */ +#define DEFAULT_Z_ALG "ZIP" +#define DEFAULT_Z_LEVEL 6 + +/* Default AEAD algorithm */ +#define DEFAULT_AEAD_ALG PGP_AEAD_OCB + +/* Default AEAD chunk bits, equals to 256K chunks */ +#define DEFAULT_AEAD_CHUNK_BITS 12 + +/* Default cipher mode for secret key encryption */ +#define DEFAULT_CIPHER_MODE "CFB" + +/* Default cipher mode for secret key encryption */ +#define DEFAULT_PGP_CIPHER_MODE PGP_CIPHER_MODE_CFB + +/* Default public key algorithm for new key generation */ +#define DEFAULT_PK_ALG PGP_PKA_RSA + +/* Default RSA key length */ +#define DEFAULT_RSA_NUMBITS 2048 + +/* Default ElGamal key length */ +#define DEFAULT_ELGAMAL_NUMBITS 2048 +#define ELGAMAL_MIN_P_BITLEN 1024 +#define ELGAMAL_MAX_P_BITLEN 4096 + +/* Default, min and max DSA key length */ +#define DSA_MIN_P_BITLEN 1024 +#define DSA_MAX_P_BITLEN 3072 +#define DSA_DEFAULT_P_BITLEN 2048 + +/* Default EC curve */ +#define DEFAULT_CURVE "NIST P-256" + +/* Default maximum password request attempts */ +#define MAX_PASSWORD_ATTEMPTS 3 + +/* Infinite password request attempts */ +#define INFINITE_ATTEMPTS -1 + +/* Default key expiration in seconds, 2 years */ +#define DEFAULT_KEY_EXPIRATION (2 * 365 * 24 * 60 * 60) + +#endif diff --git a/src/lib/ffi-priv-types.h b/src/lib/ffi-priv-types.h new file mode 100644 index 0000000..beb624c --- /dev/null +++ b/src/lib/ffi-priv-types.h @@ -0,0 +1,240 @@ +/*- + * Copyright (c) 2019 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> +#include <json.h> +#include "utils.h" +#include <list> +#include <crypto/mem.h> +#include "sec_profile.hpp" + +struct rnp_key_handle_st { + rnp_ffi_t ffi; + pgp_key_search_t locator; + pgp_key_t * pub; + pgp_key_t * sec; +}; + +struct rnp_uid_handle_st { + rnp_ffi_t ffi; + pgp_key_t *key; + size_t idx; +}; + +struct rnp_signature_handle_st { + rnp_ffi_t ffi; + const pgp_key_t *key; + pgp_subsig_t * sig; + bool own_sig; +}; + +struct rnp_recipient_handle_st { + rnp_ffi_t ffi; + uint8_t keyid[PGP_KEY_ID_SIZE]; + pgp_pubkey_alg_t palg; +}; + +struct rnp_symenc_handle_st { + rnp_ffi_t ffi; + pgp_symm_alg_t alg; + pgp_hash_alg_t halg; + pgp_s2k_specifier_t s2k_type; + uint32_t iterations; + pgp_aead_alg_t aalg; +}; + +struct rnp_ffi_st { + FILE * errs; + rnp_key_store_t * pubring; + rnp_key_store_t * secring; + rnp_get_key_cb getkeycb; + void * getkeycb_ctx; + rnp_password_cb getpasscb; + void * getpasscb_ctx; + pgp_key_provider_t key_provider; + pgp_password_provider_t pass_provider; + rnp::SecurityContext context; + + rnp_ffi_st(pgp_key_store_format_t pub_fmt, pgp_key_store_format_t sec_fmt); + ~rnp_ffi_st(); + + rnp::RNG & rng() noexcept; + rnp::SecurityProfile &profile() noexcept; +}; + +struct rnp_input_st { + /* either src or src_directory are valid, not both */ + pgp_source_t src; + std::string src_directory; + rnp_input_reader_t *reader; + rnp_input_closer_t *closer; + void * app_ctx; + + rnp_input_st(); + rnp_input_st(const rnp_input_st &) = delete; + rnp_input_st(rnp_input_st &&) = delete; + ~rnp_input_st(); + + rnp_input_st &operator=(const rnp_input_st &) = delete; + rnp_input_st &operator=(rnp_input_st &&src); +}; + +struct rnp_output_st { + /* either dst or dst_directory are valid, not both */ + pgp_dest_t dst; + char * dst_directory; + rnp_output_writer_t *writer; + rnp_output_closer_t *closer; + void * app_ctx; + bool keep; +}; + +struct rnp_op_generate_st { + rnp_ffi_t ffi{}; + bool primary{}; + pgp_key_t *primary_sec{}; + pgp_key_t *primary_pub{}; + pgp_key_t *gen_sec{}; + pgp_key_t *gen_pub{}; + /* password used to encrypt the key, if specified */ + rnp::secure_vector<char> password; + /* request password for key encryption via ffi's password provider */ + bool request_password{}; + /* we don't use top-level keygen action here for easier fields access */ + rnp_keygen_crypto_params_t crypto{}; + rnp_key_protection_params_t protection{}; + rnp_selfsig_cert_info_t cert{}; + rnp_selfsig_binding_info_t binding{}; +}; + +struct rnp_op_sign_signature_st { + rnp_ffi_t ffi{}; + rnp_signer_info_t signer{}; + bool expiry_set : 1; + bool create_set : 1; + bool hash_set : 1; +}; + +typedef std::list<rnp_op_sign_signature_st> rnp_op_sign_signatures_t; + +struct rnp_op_sign_st { + rnp_ffi_t ffi{}; + rnp_input_t input{}; + rnp_output_t output{}; + rnp_ctx_t rnpctx{}; + rnp_op_sign_signatures_t signatures{}; +}; + +struct rnp_op_verify_signature_st { + rnp_ffi_t ffi; + rnp_result_t verify_status; + pgp_signature_t sig_pkt; +}; + +struct rnp_op_verify_st { + rnp_ffi_t ffi{}; + rnp_input_t input{}; + rnp_input_t detached_input{}; /* for detached signature will be source file/data */ + rnp_output_t output{}; + rnp_ctx_t rnpctx{}; + /* these fields are filled after operation execution */ + rnp_op_verify_signature_t signatures{}; + size_t signature_count{}; + char * filename{}; + uint32_t file_mtime{}; + /* encryption information */ + bool encrypted{}; + bool mdc{}; + bool validated{}; + pgp_aead_alg_t aead{}; + pgp_symm_alg_t salg{}; + bool ignore_sigs{}; + bool require_all_sigs{}; + bool allow_hidden{}; + /* recipient/symenc information */ + rnp_recipient_handle_t recipients{}; + size_t recipient_count{}; + rnp_recipient_handle_t used_recipient{}; + rnp_symenc_handle_t symencs{}; + size_t symenc_count{}; + rnp_symenc_handle_t used_symenc{}; + size_t encrypted_layers{}; + + ~rnp_op_verify_st(); +}; + +struct rnp_op_encrypt_st { + rnp_ffi_t ffi{}; + rnp_input_t input{}; + rnp_output_t output{}; + rnp_ctx_t rnpctx{}; + rnp_op_sign_signatures_t signatures{}; +}; + +#define RNP_LOCATOR_MAX_SIZE (MAX_ID_LENGTH + 1) +static_assert(RNP_LOCATOR_MAX_SIZE > PGP_FINGERPRINT_SIZE * 2, "Locator size mismatch."); +static_assert(RNP_LOCATOR_MAX_SIZE > PGP_KEY_ID_SIZE * 2, "Locator size mismatch."); +static_assert(RNP_LOCATOR_MAX_SIZE > PGP_KEY_GRIP_SIZE * 2, "Locator size mismatch."); +static_assert(RNP_LOCATOR_MAX_SIZE > MAX_ID_LENGTH, "Locator size mismatch."); + +struct rnp_identifier_iterator_st { + rnp_ffi_t ffi; + pgp_key_search_type_t type; + rnp_key_store_t * store; + std::list<pgp_key_t>::iterator *keyp; + unsigned uididx; + json_object * tbl; + char buf[RNP_LOCATOR_MAX_SIZE]; +}; + +struct rnp_decryption_kp_param_t { + rnp_op_verify_t op; + bool has_hidden; /* key provider had hidden keyid request */ + pgp_key_t * last; /* last key, returned in hidden keyid request */ + + rnp_decryption_kp_param_t(rnp_op_verify_t opobj) + : op(opobj), has_hidden(false), last(NULL){}; +}; + +/* This is just for readability at the call site and will hopefully reduce mistakes. + * + * Instead of: + * void do_something(rnp_ffi_t ffi, bool with_secret_keys); + * do_something(ffi, true); + * do_something(ffi, false); + * + * You can have something a bit clearer: + * void do_something(rnp_ffi_t ffi, key_type_t key_type); + * do_something(ffi, KEY_TYPE_PUBLIC); + * do_something(ffi, KEY_TYPE_SECRET); + */ +typedef enum key_type_t { + KEY_TYPE_NONE, + KEY_TYPE_PUBLIC, + KEY_TYPE_SECRET, + KEY_TYPE_ANY +} key_type_t; diff --git a/src/lib/fingerprint.cpp b/src/lib/fingerprint.cpp new file mode 100644 index 0000000..c937c74 --- /dev/null +++ b/src/lib/fingerprint.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017-2022, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include "fingerprint.h" +#include "crypto/hash.hpp" +#include <librepgp/stream-key.h> +#include <librepgp/stream-sig.h> +#include <librepgp/stream-packet.h> +#include "utils.h" + +rnp_result_t +pgp_fingerprint(pgp_fingerprint_t &fp, const pgp_key_pkt_t &key) +{ + if ((key.version == PGP_V2) || (key.version == PGP_V3)) { + if (!is_rsa_key_alg(key.alg)) { + RNP_LOG("bad algorithm"); + return RNP_ERROR_NOT_SUPPORTED; + } + try { + auto hash = rnp::Hash::create(PGP_HASH_MD5); + hash->add(key.material.rsa.n); + hash->add(key.material.rsa.e); + fp.length = hash->finish(fp.fingerprint); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("Failed to calculate v3 fingerprint: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } + } + + if (key.version != PGP_V4) { + RNP_LOG("unsupported key version"); + return RNP_ERROR_NOT_SUPPORTED; + } + + try { + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + signature_hash_key(key, *hash); + fp.length = hash->finish(fp.fingerprint); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("Failed to calculate v4 fingerprint: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } +} + +/** + * \ingroup Core_Keys + * \brief Calculate the Key ID from the public key. + * \param keyid Space for the calculated ID to be stored + * \param key The key for which the ID is calculated + */ + +rnp_result_t +pgp_keyid(pgp_key_id_t &keyid, const pgp_key_pkt_t &key) +{ + pgp_fingerprint_t fp; + rnp_result_t ret; + size_t n; + + if ((key.version == PGP_V2) || (key.version == PGP_V3)) { + if (!is_rsa_key_alg(key.alg)) { + RNP_LOG("bad algorithm"); + return RNP_ERROR_NOT_SUPPORTED; + } + n = mpi_bytes(&key.material.rsa.n); + (void) memcpy(keyid.data(), key.material.rsa.n.mpi + n - keyid.size(), keyid.size()); + return RNP_SUCCESS; + } + + if ((ret = pgp_fingerprint(fp, key))) { + return ret; + } + (void) memcpy(keyid.data(), fp.fingerprint + fp.length - keyid.size(), keyid.size()); + return RNP_SUCCESS; +} + +bool +pgp_fingerprint_t::operator==(const pgp_fingerprint_t &src) const +{ + return (length == src.length) && !memcmp(fingerprint, src.fingerprint, length); +} + +bool +pgp_fingerprint_t::operator!=(const pgp_fingerprint_t &src) const +{ + return !(*this == src); +} diff --git a/src/lib/fingerprint.h b/src/lib/fingerprint.h new file mode 100644 index 0000000..e8d4713 --- /dev/null +++ b/src/lib/fingerprint.h @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 2017 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_FINGERPRINT_H_ +#define RNP_FINGERPRINT_H_ + +#include <rnp/rnp_def.h> +#include <stdint.h> +#include <stdlib.h> +#include "types.h" + +rnp_result_t pgp_fingerprint(pgp_fingerprint_t &fp, const pgp_key_pkt_t &key); + +rnp_result_t pgp_keyid(pgp_key_id_t &keyid, const pgp_key_pkt_t &key); + +#endif diff --git a/src/lib/generate-key.cpp b/src/lib/generate-key.cpp new file mode 100644 index 0000000..dfd5556 --- /dev/null +++ b/src/lib/generate-key.cpp @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include <stdbool.h> +#include <stdint.h> +#include <assert.h> +#include <string.h> + +#include <rekey/rnp_key_store.h> +#include <librekey/key_store_pgp.h> +#include <librekey/key_store_g10.h> +#include <librepgp/stream-packet.h> +#include "crypto.h" +#include "pgp-key.h" +#include "defaults.h" +#include "utils.h" + +static const uint8_t DEFAULT_SYMMETRIC_ALGS[] = { + PGP_SA_AES_256, PGP_SA_AES_192, PGP_SA_AES_128}; +static const uint8_t DEFAULT_HASH_ALGS[] = { + PGP_HASH_SHA256, PGP_HASH_SHA384, PGP_HASH_SHA512, PGP_HASH_SHA224}; +static const uint8_t DEFAULT_COMPRESS_ALGS[] = { + PGP_C_ZLIB, PGP_C_BZIP2, PGP_C_ZIP, PGP_C_NONE}; + +static const id_str_pair pubkey_alg_map[] = { + {PGP_PKA_RSA, "RSA (Encrypt or Sign)"}, + {PGP_PKA_RSA_ENCRYPT_ONLY, "RSA Encrypt-Only"}, + {PGP_PKA_RSA_SIGN_ONLY, "RSA Sign-Only"}, + {PGP_PKA_ELGAMAL, "Elgamal (Encrypt-Only)"}, + {PGP_PKA_DSA, "DSA"}, + {PGP_PKA_ECDH, "ECDH"}, + {PGP_PKA_ECDSA, "ECDSA"}, + {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, "Reserved (formerly Elgamal Encrypt or Sign"}, + {PGP_PKA_RESERVED_DH, "Reserved for Diffie-Hellman (X9.42)"}, + {PGP_PKA_EDDSA, "EdDSA"}, + {PGP_PKA_SM2, "SM2"}, + {PGP_PKA_PRIVATE00, "Private/Experimental"}, + {PGP_PKA_PRIVATE01, "Private/Experimental"}, + {PGP_PKA_PRIVATE02, "Private/Experimental"}, + {PGP_PKA_PRIVATE03, "Private/Experimental"}, + {PGP_PKA_PRIVATE04, "Private/Experimental"}, + {PGP_PKA_PRIVATE05, "Private/Experimental"}, + {PGP_PKA_PRIVATE06, "Private/Experimental"}, + {PGP_PKA_PRIVATE07, "Private/Experimental"}, + {PGP_PKA_PRIVATE08, "Private/Experimental"}, + {PGP_PKA_PRIVATE09, "Private/Experimental"}, + {PGP_PKA_PRIVATE10, "Private/Experimental"}, + {0, NULL}}; + +static bool +load_generated_g10_key(pgp_key_t * dst, + pgp_key_pkt_t * newkey, + pgp_key_t * primary_key, + pgp_key_t * pubkey, + rnp::SecurityContext &ctx) +{ + // this should generally be zeroed + assert(dst->type() == 0); + // if a primary is provided, make sure it's actually a primary key + assert(!primary_key || primary_key->is_primary()); + // if a pubkey is provided, make sure it's actually a public key + assert(!pubkey || pubkey->is_public()); + // G10 always needs pubkey here + assert(pubkey); + + // this would be better on the stack but the key store does not allow it + std::unique_ptr<rnp_key_store_t> key_store(new (std::nothrow) rnp_key_store_t(ctx)); + if (!key_store) { + return false; + } + /* Write g10 seckey */ + rnp::MemoryDest memdst(NULL, 0); + if (!g10_write_seckey(&memdst.dst(), newkey, NULL, ctx)) { + RNP_LOG("failed to write generated seckey"); + return false; + } + + std::vector<pgp_key_t *> key_ptrs; /* holds primary and pubkey, when used */ + // if this is a subkey, add the primary in first + if (primary_key) { + key_ptrs.push_back(primary_key); + } + // G10 needs the pubkey for copying some attributes (key version, creation time, etc) + key_ptrs.push_back(pubkey); + + rnp::MemorySource memsrc(memdst.memory(), memdst.writeb(), false); + pgp_key_provider_t prov(rnp_key_provider_key_ptr_list, &key_ptrs); + if (!rnp_key_store_g10_from_src(key_store.get(), &memsrc.src(), &prov)) { + return false; + } + if (rnp_key_store_get_key_count(key_store.get()) != 1) { + return false; + } + // if a primary key is provided, it should match the sub with regards to type + assert(!primary_key || (primary_key->is_secret() == key_store->keys.front().is_secret())); + *dst = pgp_key_t(key_store->keys.front()); + return true; +} + +static uint8_t +pk_alg_default_flags(pgp_pubkey_alg_t alg) +{ + // just use the full capabilities as the ultimate fallback + return pgp_pk_alg_capabilities(alg); +} + +// TODO: Similar as pgp_pick_hash_alg but different enough to +// keep another version. This will be changed when refactoring crypto +static void +adjust_hash_alg(rnp_keygen_crypto_params_t &crypto) +{ + if (!crypto.hash_alg) { + crypto.hash_alg = (pgp_hash_alg_t) DEFAULT_HASH_ALGS[0]; + } + + if ((crypto.key_alg != PGP_PKA_DSA) && (crypto.key_alg != PGP_PKA_ECDSA)) { + return; + } + + pgp_hash_alg_t min_hash = (crypto.key_alg == PGP_PKA_ECDSA) ? + ecdsa_get_min_hash(crypto.ecc.curve) : + dsa_get_min_hash(crypto.dsa.q_bitlen); + + if (rnp::Hash::size(crypto.hash_alg) < rnp::Hash::size(min_hash)) { + crypto.hash_alg = min_hash; + } +} + +static void +keygen_merge_crypto_defaults(rnp_keygen_crypto_params_t &crypto) +{ + // default to RSA + if (!crypto.key_alg) { + crypto.key_alg = PGP_PKA_RSA; + } + + switch (crypto.key_alg) { + case PGP_PKA_RSA: + if (!crypto.rsa.modulus_bit_len) { + crypto.rsa.modulus_bit_len = DEFAULT_RSA_NUMBITS; + } + break; + + case PGP_PKA_SM2: + if (!crypto.hash_alg) { + crypto.hash_alg = PGP_HASH_SM3; + } + if (!crypto.ecc.curve) { + crypto.ecc.curve = PGP_CURVE_SM2_P_256; + } + break; + + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: { + if (!crypto.hash_alg) { + crypto.hash_alg = (pgp_hash_alg_t) DEFAULT_HASH_ALGS[0]; + } + break; + } + + case PGP_PKA_EDDSA: + if (!crypto.ecc.curve) { + crypto.ecc.curve = PGP_CURVE_ED25519; + } + break; + + case PGP_PKA_DSA: { + if (!crypto.dsa.p_bitlen) { + crypto.dsa.p_bitlen = DSA_DEFAULT_P_BITLEN; + } + if (!crypto.dsa.q_bitlen) { + crypto.dsa.q_bitlen = dsa_choose_qsize_by_psize(crypto.dsa.p_bitlen); + } + break; + } + default: + break; + } + + adjust_hash_alg(crypto); +} + +static bool +validate_keygen_primary(const rnp_keygen_primary_desc_t &desc) +{ + /* Confirm that the specified pk alg can certify. + * gpg requires this, though the RFC only says that a V4 primary + * key SHOULD be a key capable of certification. + */ + if (!(pgp_pk_alg_capabilities(desc.crypto.key_alg) & PGP_KF_CERTIFY)) { + RNP_LOG("primary key alg (%d) must be able to sign", desc.crypto.key_alg); + return false; + } + + // check key flags + if (!desc.cert.key_flags) { + // these are probably not *technically* required + RNP_LOG("key flags are required"); + return false; + } else if (desc.cert.key_flags & ~pgp_pk_alg_capabilities(desc.crypto.key_alg)) { + // check the flags against the alg capabilities + RNP_LOG("usage not permitted for pk algorithm"); + return false; + } + // require a userid + if (!desc.cert.userid[0]) { + RNP_LOG("userid is required for primary key"); + return false; + } + return true; +} + +static uint32_t +get_numbits(const rnp_keygen_crypto_params_t *crypto) +{ + switch (crypto->key_alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return crypto->rsa.modulus_bit_len; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + if (const ec_curve_desc_t *curve = get_curve_desc(crypto->ecc.curve)) { + return curve->bitlen; + } else { + return 0; + } + } + case PGP_PKA_DSA: + return crypto->dsa.p_bitlen; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return crypto->elgamal.key_bitlen; + default: + return 0; + } +} + +static void +set_default_user_prefs(pgp_user_prefs_t &prefs) +{ + if (prefs.symm_algs.empty()) { + prefs.set_symm_algs( + std::vector<uint8_t>(DEFAULT_SYMMETRIC_ALGS, + DEFAULT_SYMMETRIC_ALGS + ARRAY_SIZE(DEFAULT_SYMMETRIC_ALGS))); + } + if (prefs.hash_algs.empty()) { + prefs.set_hash_algs(std::vector<uint8_t>( + DEFAULT_HASH_ALGS, DEFAULT_HASH_ALGS + ARRAY_SIZE(DEFAULT_HASH_ALGS))); + } + if (prefs.z_algs.empty()) { + prefs.set_z_algs(std::vector<uint8_t>( + DEFAULT_COMPRESS_ALGS, DEFAULT_COMPRESS_ALGS + ARRAY_SIZE(DEFAULT_COMPRESS_ALGS))); + } +} + +static void +keygen_primary_merge_defaults(rnp_keygen_primary_desc_t &desc) +{ + keygen_merge_crypto_defaults(desc.crypto); + set_default_user_prefs(desc.cert.prefs); + + if (!desc.cert.key_flags) { + // set some default key flags if none are provided + desc.cert.key_flags = pk_alg_default_flags(desc.crypto.key_alg); + } + if (desc.cert.userid.empty()) { + char uid[MAX_ID_LENGTH] = {0}; + snprintf(uid, + sizeof(uid), + "%s %d-bit key <%s@localhost>", + id_str_pair::lookup(pubkey_alg_map, desc.crypto.key_alg), + get_numbits(&desc.crypto), + getenv_logname()); + desc.cert.userid = uid; + } +} + +bool +pgp_generate_primary_key(rnp_keygen_primary_desc_t &desc, + bool merge_defaults, + pgp_key_t & primary_sec, + pgp_key_t & primary_pub, + pgp_key_store_format_t secformat) +{ + // validate args + if (primary_sec.type() || primary_pub.type()) { + RNP_LOG("invalid parameters (should be zeroed)"); + return false; + } + + try { + // merge some defaults in, if requested + if (merge_defaults) { + keygen_primary_merge_defaults(desc); + } + // now validate the keygen fields + if (!validate_keygen_primary(desc)) { + return false; + } + + // generate the raw key and fill tag/secret fields + pgp_key_pkt_t secpkt; + if (!pgp_generate_seckey(desc.crypto, secpkt, true)) { + return false; + } + + pgp_key_t sec(secpkt); + pgp_key_t pub(secpkt, true); + sec.add_uid_cert(desc.cert, desc.crypto.hash_alg, *desc.crypto.ctx, &pub); + + switch (secformat) { + case PGP_KEY_STORE_GPG: + case PGP_KEY_STORE_KBX: + primary_sec = std::move(sec); + primary_pub = std::move(pub); + break; + case PGP_KEY_STORE_G10: + primary_pub = std::move(pub); + if (!load_generated_g10_key( + &primary_sec, &secpkt, NULL, &primary_pub, *desc.crypto.ctx)) { + RNP_LOG("failed to load generated key"); + return false; + } + break; + default: + RNP_LOG("invalid format"); + return false; + } + } catch (const std::exception &e) { + RNP_LOG("Failure: %s", e.what()); + return false; + } + + /* mark it as valid */ + primary_pub.mark_valid(); + primary_sec.mark_valid(); + /* refresh key's data */ + return primary_pub.refresh_data(*desc.crypto.ctx) && + primary_sec.refresh_data(*desc.crypto.ctx); +} + +static bool +validate_keygen_subkey(rnp_keygen_subkey_desc_t &desc) +{ + if (!desc.binding.key_flags) { + RNP_LOG("key flags are required"); + return false; + } else if (desc.binding.key_flags & ~pgp_pk_alg_capabilities(desc.crypto.key_alg)) { + // check the flags against the alg capabilities + RNP_LOG("usage not permitted for pk algorithm"); + return false; + } + return true; +} + +static void +keygen_subkey_merge_defaults(rnp_keygen_subkey_desc_t &desc) +{ + keygen_merge_crypto_defaults(desc.crypto); + if (!desc.binding.key_flags) { + // set some default key flags if none are provided + desc.binding.key_flags = pk_alg_default_flags(desc.crypto.key_alg); + } +} + +bool +pgp_generate_subkey(rnp_keygen_subkey_desc_t & desc, + bool merge_defaults, + pgp_key_t & primary_sec, + pgp_key_t & primary_pub, + pgp_key_t & subkey_sec, + pgp_key_t & subkey_pub, + const pgp_password_provider_t &password_provider, + pgp_key_store_format_t secformat) +{ + // validate args + if (!primary_sec.is_primary() || !primary_pub.is_primary() || !primary_sec.is_secret() || + !primary_pub.is_public()) { + RNP_LOG("invalid parameters"); + return false; + } + if (subkey_sec.type() || subkey_pub.type()) { + RNP_LOG("invalid parameters (should be zeroed)"); + return false; + } + + // merge some defaults in, if requested + if (merge_defaults) { + keygen_subkey_merge_defaults(desc); + } + + // now validate the keygen fields + if (!validate_keygen_subkey(desc)) { + return false; + } + + try { + /* decrypt the primary seckey if needed (for signatures) */ + rnp::KeyLocker primlock(primary_sec); + if (primary_sec.encrypted() && + !primary_sec.unlock(password_provider, PGP_OP_ADD_SUBKEY)) { + RNP_LOG("Failed to unlock primary key."); + return false; + } + /* generate the raw subkey */ + pgp_key_pkt_t secpkt; + if (!pgp_generate_seckey(desc.crypto, secpkt, false)) { + return false; + } + pgp_key_pkt_t pubpkt = pgp_key_pkt_t(secpkt, true); + pgp_key_t sec(secpkt, primary_sec); + pgp_key_t pub(pubpkt, primary_pub); + /* add binding */ + primary_sec.add_sub_binding( + sec, pub, desc.binding, desc.crypto.hash_alg, *desc.crypto.ctx); + /* copy to the result */ + subkey_pub = std::move(pub); + switch (secformat) { + case PGP_KEY_STORE_GPG: + case PGP_KEY_STORE_KBX: + subkey_sec = std::move(sec); + break; + case PGP_KEY_STORE_G10: + if (!load_generated_g10_key( + &subkey_sec, &secpkt, &primary_sec, &subkey_pub, *desc.crypto.ctx)) { + RNP_LOG("failed to load generated key"); + return false; + } + break; + default: + RNP_LOG("invalid format"); + return false; + } + + subkey_pub.mark_valid(); + subkey_sec.mark_valid(); + return subkey_pub.refresh_data(&primary_pub, *desc.crypto.ctx) && + subkey_sec.refresh_data(&primary_sec, *desc.crypto.ctx); + } catch (const std::exception &e) { + RNP_LOG("Subkey generation failed: %s", e.what()); + return false; + } +} diff --git a/src/lib/json-utils.cpp b/src/lib/json-utils.cpp new file mode 100644 index 0000000..742fbc1 --- /dev/null +++ b/src/lib/json-utils.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "json-utils.h" +#include "logging.h" +#include "crypto/mem.h" + +/* Shortcut function to add field checking it for null to avoid allocation failure. + Please note that it deallocates val on failure. */ +bool +obj_add_field_json(json_object *obj, const char *name, json_object *val) +{ + if (!val) { + return false; + } + // TODO: in JSON-C 0.13 json_object_object_add returns bool instead of void + json_object_object_add(obj, name, val); + if (!json_object_object_get_ex(obj, name, NULL)) { + json_object_put(val); + return false; + } + + return true; +} + +bool +json_add(json_object *obj, const char *name, const char *value) +{ + return obj_add_field_json(obj, name, json_object_new_string(value)); +} + +bool +json_add(json_object *obj, const char *name, bool value) +{ + return obj_add_field_json(obj, name, json_object_new_boolean(value)); +} + +bool +json_add(json_object *obj, const char *name, const char *value, size_t len) +{ + return obj_add_field_json(obj, name, json_object_new_string_len(value, len)); +} + +bool +obj_add_hex_json(json_object *obj, const char *name, const uint8_t *val, size_t val_len) +{ + if (val_len > 1024 * 1024) { + RNP_LOG("too large json hex field: %zu", val_len); + val_len = 1024 * 1024; + } + + char smallbuf[64] = {0}; + size_t hexlen = val_len * 2 + 1; + + char *hexbuf = hexlen < sizeof(smallbuf) ? smallbuf : (char *) malloc(hexlen); + if (!hexbuf) { + return false; + } + + bool res = rnp::hex_encode(val, val_len, hexbuf, hexlen, rnp::HEX_LOWERCASE) && + obj_add_field_json(obj, name, json_object_new_string(hexbuf)); + + if (hexbuf != smallbuf) { + free(hexbuf); + } + return res; +} + +bool +array_add_element_json(json_object *obj, json_object *val) +{ + if (!val) { + return false; + } + if (json_object_array_add(obj, val)) { + json_object_put(val); + return false; + } + return true; +} diff --git a/src/lib/json-utils.h b/src/lib/json-utils.h new file mode 100644 index 0000000..c60615b --- /dev/null +++ b/src/lib/json-utils.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_JSON_UTILS_H_ +#define RNP_JSON_UTILS_H_ + +#include <stdio.h> +#include "types.h" +#include <limits.h> +#include "json_object.h" +#include "json.h" + +/** + * @brief Add field to the json object. + * Note: this function is for convenience, it will check val for NULL and destroy val + * on failure. + * @param obj allocated json_object of object type. + * @param name name of the field + * @param val json object of any type. Will be checked for NULL. + * @return true if val is not NULL and field was added successfully, false otherwise. + */ +bool obj_add_field_json(json_object *obj, const char *name, json_object *val); + +/** + * @brief Shortcut to add string via obj_add_field_json(). + */ +bool json_add(json_object *obj, const char *name, const char *value); + +/** + * @brief Shortcut to add string with length via obj_add_field_json(). + */ +bool json_add(json_object *obj, const char *name, const char *value, size_t len); + +/** + * @brief Shortcut to add bool via obj_add_field_json(). + */ +bool json_add(json_object *obj, const char *name, bool value); + +/** + * @brief Add hex representation of binary data as string field to JSON object. + * Note: this function follows conventions of obj_add_field_json(). + */ +bool obj_add_hex_json(json_object *obj, const char *name, const uint8_t *val, size_t val_len); + +/** + * @brief Add element to JSON array. + * Note: this function follows convention of the obj_add_field_json. + */ +bool array_add_element_json(json_object *obj, json_object *val); + +namespace rnp { +class JSONObject { + json_object *obj_; + + public: + JSONObject(json_object *obj) : obj_(obj) + { + } + + ~JSONObject() + { + if (obj_) { + json_object_put(obj_); + } + } +}; +} // namespace rnp + +#endif diff --git a/src/lib/key-provider.cpp b/src/lib/key-provider.cpp new file mode 100644 index 0000000..2a64b63 --- /dev/null +++ b/src/lib/key-provider.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <assert.h> +#include <string.h> +#include "key-provider.h" +#include "pgp-key.h" +#include "fingerprint.h" +#include "types.h" +#include "utils.h" +#include <rekey/rnp_key_store.h> + +bool +rnp_key_matches_search(const pgp_key_t *key, const pgp_key_search_t *search) +{ + if (!key) { + return false; + } + switch (search->type) { + case PGP_KEY_SEARCH_KEYID: + return (key->keyid() == search->by.keyid) || (search->by.keyid == pgp_key_id_t({})); + case PGP_KEY_SEARCH_FINGERPRINT: + return key->fp() == search->by.fingerprint; + case PGP_KEY_SEARCH_GRIP: + return key->grip() == search->by.grip; + case PGP_KEY_SEARCH_USERID: + if (key->has_uid(search->by.userid)) { + return true; + } + break; + default: + assert(false); + break; + } + return false; +} + +pgp_key_t * +pgp_request_key(const pgp_key_provider_t *provider, const pgp_key_request_ctx_t *ctx) +{ + pgp_key_t *key = NULL; + if (!provider || !provider->callback || !ctx) { + return NULL; + } + if (!(key = provider->callback(ctx, provider->userdata))) { + return NULL; + } + // confirm that the key actually matches the search criteria + if (!rnp_key_matches_search(key, &ctx->search) && key->is_secret() == ctx->secret) { + return NULL; + } + return key; +} + +pgp_key_t * +rnp_key_provider_key_ptr_list(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + std::vector<pgp_key_t *> *key_list = (std::vector<pgp_key_t *> *) userdata; + for (auto key : *key_list) { + if (rnp_key_matches_search(key, &ctx->search) && (key->is_secret() == ctx->secret)) { + return key; + } + } + return NULL; +} + +pgp_key_t * +rnp_key_provider_chained(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + for (pgp_key_provider_t **pprovider = (pgp_key_provider_t **) userdata; + pprovider && *pprovider; + pprovider++) { + pgp_key_provider_t *provider = *pprovider; + pgp_key_t * key = NULL; + if ((key = provider->callback(ctx, provider->userdata))) { + return key; + } + } + return NULL; +} + +pgp_key_t * +rnp_key_provider_store(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + rnp_key_store_t *ks = (rnp_key_store_t *) userdata; + + for (pgp_key_t *key = rnp_key_store_search(ks, &ctx->search, NULL); key; + key = rnp_key_store_search(ks, &ctx->search, key)) { + if (key->is_secret() == ctx->secret) { + return key; + } + } + return NULL; +} diff --git a/src/lib/key-provider.h b/src/lib/key-provider.h new file mode 100644 index 0000000..4d09e2f --- /dev/null +++ b/src/lib/key-provider.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_KEY_PROVIDER_H +#define RNP_KEY_PROVIDER_H + +#include "types.h" +#include "fingerprint.h" + +typedef struct pgp_key_t pgp_key_t; + +typedef enum { + PGP_KEY_SEARCH_UNKNOWN, + PGP_KEY_SEARCH_KEYID, + PGP_KEY_SEARCH_FINGERPRINT, + PGP_KEY_SEARCH_GRIP, + PGP_KEY_SEARCH_USERID +} pgp_key_search_type_t; + +typedef struct pgp_key_search_t { + pgp_key_search_type_t type; + union { + pgp_key_id_t keyid; + pgp_key_grip_t grip; + pgp_fingerprint_t fingerprint; + char userid[MAX_ID_LENGTH + 1]; + } by; + + pgp_key_search_t(pgp_key_search_type_t atype = PGP_KEY_SEARCH_UNKNOWN) : type(atype){}; +} pgp_key_search_t; + +typedef struct pgp_key_request_ctx_t { + pgp_op_t op; + bool secret; + pgp_key_search_t search; + + pgp_key_request_ctx_t(pgp_op_t anop = PGP_OP_UNKNOWN, + bool sec = false, + pgp_key_search_type_t tp = PGP_KEY_SEARCH_UNKNOWN) + : op(anop), secret(sec) + { + search.type = tp; + } +} pgp_key_request_ctx_t; + +typedef pgp_key_t *pgp_key_callback_t(const pgp_key_request_ctx_t *ctx, void *userdata); + +typedef struct pgp_key_provider_t { + pgp_key_callback_t *callback; + void * userdata; + + pgp_key_provider_t(pgp_key_callback_t *cb = NULL, void *ud = NULL) + : callback(cb), userdata(ud){}; +} pgp_key_provider_t; + +/** checks if a key matches search criteria + * + * Note that this does not do any check on the type of key (public/secret), + * that is left up to the caller. + * + * @param key the key to check + * @param search the search criteria to check against + * @return true if the key satisfies the search criteria, false otherwise + **/ +bool rnp_key_matches_search(const pgp_key_t *key, const pgp_key_search_t *search); + +/** @brief request public or secret pgp key, according to information stored in ctx + * @param ctx information about the request - which operation requested the key, which search + * criteria should be used and whether secret or public key is needed + * @param key pointer to the key structure will be stored here on success + * @return a key pointer on success, or NULL if key was not found otherwise + **/ +pgp_key_t *pgp_request_key(const pgp_key_provider_t * provider, + const pgp_key_request_ctx_t *ctx); + +/** key provider callback that searches a list of pgp_key_t pointers + * + * @param ctx + * @param userdata must be a list of key pgp_key_t** + */ +pgp_key_t *rnp_key_provider_key_ptr_list(const pgp_key_request_ctx_t *ctx, void *userdata); + +/** key provider callback that searches a given store + * + * @param ctx + * @param userdata must be a pointer to rnp_key_store_t + */ +pgp_key_t *rnp_key_provider_store(const pgp_key_request_ctx_t *ctx, void *userdata); + +/** key provider that calls other key providers + * + * @param ctx + * @param userdata must be an array pgp_key_provider_t pointers, + * ending with a NULL. + */ +pgp_key_t *rnp_key_provider_chained(const pgp_key_request_ctx_t *ctx, void *userdata); + +#endif diff --git a/src/lib/librnp.3.adoc b/src/lib/librnp.3.adoc new file mode 100644 index 0000000..9af84ab --- /dev/null +++ b/src/lib/librnp.3.adoc @@ -0,0 +1,89 @@ += librnp(3) +RNP +:doctype: manpage +:release-version: {component-version} +:man manual: RNP Manual +:man source: RNP {release-version} + +== NAME + +librnp - OpenPGP implementation, available via FFI interface. + +== SYNOPSIS + +*#include <rnp/rnp.h>* + +*#include <rnp/rnp_err.h>* + + +== DESCRIPTION + +*librnp* is part of the *RNP* suite and forms the basis for the _rnp(1)_ and _rnpkeys(1)_ command-line utilities. + +It provides an FFI interface to functions required for operations needed by the OpenPGP protocol. + +Interface to the library is exposed via _<rnp/rnp.h>_ and _<rnp/rnp_err.h>_ headers. +You will also need to link to _librnp_. + +Please see its headers for the full function list and detailed documentation. + +== EXAMPLES + +A number of examples are provided in *src/examples* folder of the *RNP* suite source tree. + +*generate.c*:: +Demonstrates generation of an OpenPGP keypair using the JSON key description mechanism. +May be used to generate any custom key types that are supported by the *RNP* suite. + +*encrypt.c*:: +Demonstrates how to build OpenPGP-encrypted messages. +A message is encrypted with keys, generated via *./generate*, with a hardcoded password. + +*decrypt.c*:: +Demonstrates how to decrypt OpenPGP messages. +Running this example requires the *./encrypt* example to be first run +in order to produce the sample encrypted message for decryption. + +*sign.c*:: +Demonstrates how to sign OpenPGP messages. +Running this example requires the *./generate* example to be first run +in order to generate and write out secret keys. + +*verify.c*:: +Demonstrates verify OpenPGP signed messages. +Again, running this example requires the *./sign* example to be first run +in order to generate a signed OpenPGP message. + +== BUGS + +Please report _issues_ via the RNP public issue tracker at: +https://github.com/rnpgp/rnp/issues. + +_Security reports_ or _security-sensitive feedback_ should be reported +according to the instructions at: +https://www.rnpgp.org/feedback. + + +== AUTHORS + +*RNP* is an open source project led by Ribose and has +received contributions from numerous individuals and +organizations. + + +== RESOURCES + +*Web site*: https://www.rnpgp.org + +*Source repository*: https://github.com/rnpgp/rnp + + +== COPYING + +Copyright \(C) 2017-2021 Ribose. +The RNP software suite is _freely licensed_: +please refer to the *LICENSE* file for details. + + +== SEE ALSO + +*rnp(1)*, *rnpkeys(1)* diff --git a/src/lib/librnp.symbols b/src/lib/librnp.symbols new file mode 100644 index 0000000..d8667ce --- /dev/null +++ b/src/lib/librnp.symbols @@ -0,0 +1 @@ +_rnp_* diff --git a/src/lib/librnp.vsc b/src/lib/librnp.vsc new file mode 100644 index 0000000..460db98 --- /dev/null +++ b/src/lib/librnp.vsc @@ -0,0 +1,4 @@ +{ + global: rnp_*; + local: *; +}; diff --git a/src/lib/logging.cpp b/src/lib/logging.cpp new file mode 100644 index 0000000..74c67e3 --- /dev/null +++ b/src/lib/logging.cpp @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 2017-2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "string.h" +#include "logging.h" + +/* -1 -- not initialized + 0 -- logging is off + 1 -- logging is on +*/ +static int8_t _rnp_log_switch = +#ifdef NDEBUG + -1 // lazy-initialize later +#else + 1 // always on in debug build +#endif + ; + +/* Temporary disable logging */ +static size_t _rnp_log_disable = 0; + +void +set_rnp_log_switch(int8_t value) +{ + _rnp_log_switch = value; +} + +bool +rnp_log_switch() +{ + if (_rnp_log_switch < 0) { + const char *var = getenv(RNP_LOG_CONSOLE); + _rnp_log_switch = (var && strcmp(var, "0")) ? 1 : 0; + } + return !_rnp_log_disable && !!_rnp_log_switch; +} + +void +rnp_log_stop() +{ + if (_rnp_log_disable < SIZE_MAX) { + _rnp_log_disable++; + } +} + +void +rnp_log_continue() +{ + if (_rnp_log_disable) { + _rnp_log_disable--; + } +} diff --git a/src/lib/logging.h b/src/lib/logging.h new file mode 100644 index 0000000..7335e57 --- /dev/null +++ b/src/lib/logging.h @@ -0,0 +1,97 @@ +/*- + * Copyright (c) 2017-2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_LOGGING_H_ +#define RNP_LOGGING_H_ + +#include <stdlib.h> +#include <stdint.h> + +/* environment variable name */ +static const char RNP_LOG_CONSOLE[] = "RNP_LOG_CONSOLE"; + +bool rnp_log_switch(); +void set_rnp_log_switch(int8_t); +void rnp_log_stop(); +void rnp_log_continue(); + +namespace rnp { +class LogStop { + bool stop_; + + public: + LogStop(bool stop = true) : stop_(stop) + { + if (stop_) { + rnp_log_stop(); + } + } + ~LogStop() + { + if (stop_) { + rnp_log_continue(); + } + } +}; +} // namespace rnp + +#define RNP_LOG_FD(fd, ...) \ + do { \ + if (!rnp_log_switch()) \ + break; \ + (void) fprintf((fd), "[%s() %s:%d] ", __func__, __FILE__, __LINE__); \ + (void) fprintf((fd), __VA_ARGS__); \ + (void) fprintf((fd), "\n"); \ + } while (0) + +#define RNP_LOG(...) RNP_LOG_FD(stderr, __VA_ARGS__) + +#define RNP_LOG_KEY(msg, key) \ + do { \ + if (!(key)) { \ + RNP_LOG(msg, "(null)"); \ + break; \ + } \ + char keyid[PGP_KEY_ID_SIZE * 2 + 1] = {0}; \ + const pgp_key_id_t &id = key->keyid(); \ + rnp::hex_encode(id.data(), id.size(), keyid, sizeof(keyid), rnp::HEX_LOWERCASE); \ + RNP_LOG(msg, keyid); \ + } while (0) + +#define RNP_LOG_KEY_PKT(msg, key) \ + do { \ + pgp_key_id_t keyid = {}; \ + if (pgp_keyid(keyid, (key))) { \ + RNP_LOG(msg, "unknown"); \ + break; \ + }; \ + char keyidhex[PGP_KEY_ID_SIZE * 2 + 1] = {0}; \ + rnp::hex_encode( \ + keyid.data(), keyid.size(), keyidhex, sizeof(keyidhex), rnp::HEX_LOWERCASE); \ + RNP_LOG(msg, keyidhex); \ + } while (0) + +#endif
\ No newline at end of file diff --git a/src/lib/pass-provider.cpp b/src/lib/pass-provider.cpp new file mode 100644 index 0000000..788fc23 --- /dev/null +++ b/src/lib/pass-provider.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 - 2019, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "pass-provider.h" +#include <stdio.h> +#include <string.h> + +bool +rnp_password_provider_string(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata) +{ + char *passc = (char *) userdata; + + if (!passc || strlen(passc) >= (password_size - 1)) { + return false; + } + + strncpy(password, passc, password_size - 1); + return true; +} + +bool +pgp_request_password(const pgp_password_provider_t *provider, + const pgp_password_ctx_t * ctx, + char * password, + size_t password_size) +{ + if (!provider || !provider->callback || !ctx || !password || !password_size) { + return false; + } + return provider->callback(ctx, password, password_size, provider->userdata); +} diff --git a/src/lib/pass-provider.h b/src/lib/pass-provider.h new file mode 100644 index 0000000..fd79fc5 --- /dev/null +++ b/src/lib/pass-provider.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017 - 2019, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_PASS_PROVIDER_H +#define RNP_PASS_PROVIDER_H + +#include <cstddef> +#include <cstdint> + +typedef struct pgp_key_t pgp_key_t; + +typedef struct pgp_password_ctx_t { + uint8_t op; + const pgp_key_t *key; + + pgp_password_ctx_t(uint8_t anop, const pgp_key_t *akey = NULL) : op(anop), key(akey){}; +} pgp_password_ctx_t; + +typedef bool pgp_password_callback_t(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata); + +typedef struct pgp_password_provider_t { + pgp_password_callback_t *callback; + void * userdata; + pgp_password_provider_t(pgp_password_callback_t *cb = NULL, void *ud = NULL) + : callback(cb), userdata(ud){}; +} pgp_password_provider_t; + +bool pgp_request_password(const pgp_password_provider_t *provider, + const pgp_password_ctx_t * ctx, + char * password, + size_t password_size); +bool rnp_password_provider_string(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata); +#endif diff --git a/src/lib/pgp-key.cpp b/src/lib/pgp-key.cpp new file mode 100644 index 0000000..4300331 --- /dev/null +++ b/src/lib/pgp-key.cpp @@ -0,0 +1,2776 @@ +/* + * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pgp-key.h" +#include "utils.h" +#include <librekey/key_store_pgp.h> +#include <librekey/key_store_g10.h> +#include "crypto.h" +#include "crypto/s2k.h" +#include "crypto/mem.h" +#include "crypto/signatures.h" +#include "fingerprint.h" + +#include <librepgp/stream-packet.h> +#include <librepgp/stream-key.h> +#include <librepgp/stream-sig.h> +#include <librepgp/stream-armor.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <time.h> +#include <algorithm> +#include <stdexcept> +#include "defaults.h" + +pgp_key_pkt_t * +pgp_decrypt_seckey_pgp(const pgp_rawpacket_t &raw, + const pgp_key_pkt_t & pubkey, + const char * password) +{ + try { + rnp::MemorySource src(raw.raw.data(), raw.raw.size(), false); + auto res = std::unique_ptr<pgp_key_pkt_t>(new pgp_key_pkt_t()); + if (res->parse(src.src()) || decrypt_secret_key(res.get(), password)) { + return NULL; + } + return res.release(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return NULL; + } +} + +/* Note that this function essentially serves two purposes. + * - In the case of a protected key, it requests a password and + * uses it to decrypt the key and fill in key->key.seckey. + * - In the case of an unprotected key, it simply re-loads + * key->key.seckey by parsing the key data in packets[0]. + */ +pgp_key_pkt_t * +pgp_decrypt_seckey(const pgp_key_t & key, + const pgp_password_provider_t &provider, + const pgp_password_ctx_t & ctx) +{ + // sanity checks + if (!key.is_secret()) { + RNP_LOG("invalid args"); + return NULL; + } + // ask the provider for a password + rnp::secure_array<char, MAX_PASSWORD_LENGTH> password; + if (key.is_protected() && + !pgp_request_password(&provider, &ctx, password.data(), password.size())) { + return NULL; + } + // attempt to decrypt with the provided password + switch (key.format) { + case PGP_KEY_STORE_GPG: + case PGP_KEY_STORE_KBX: + return pgp_decrypt_seckey_pgp(key.rawpkt(), key.pkt(), password.data()); + case PGP_KEY_STORE_G10: + return g10_decrypt_seckey(key.rawpkt(), key.pkt(), password.data()); + default: + RNP_LOG("unexpected format: %d", key.format); + return NULL; + } +} + +pgp_key_t * +pgp_sig_get_signer(const pgp_subsig_t &sig, rnp_key_store_t *keyring, pgp_key_provider_t *prov) +{ + pgp_key_request_ctx_t ctx(PGP_OP_VERIFY, false, PGP_KEY_SEARCH_UNKNOWN); + /* if we have fingerprint let's check it */ + if (sig.sig.has_keyfp()) { + ctx.search.by.fingerprint = sig.sig.keyfp(); + ctx.search.type = PGP_KEY_SEARCH_FINGERPRINT; + } else if (sig.sig.has_keyid()) { + ctx.search.by.keyid = sig.sig.keyid(); + ctx.search.type = PGP_KEY_SEARCH_KEYID; + } else { + RNP_LOG("No way to search for the signer."); + return NULL; + } + + pgp_key_t *key = rnp_key_store_search(keyring, &ctx.search, NULL); + if (key || !prov) { + return key; + } + return pgp_request_key(prov, &ctx); +} + +static const id_str_pair ss_rr_code_map[] = { + {PGP_REVOCATION_NO_REASON, "No reason specified"}, + {PGP_REVOCATION_SUPERSEDED, "Key is superseded"}, + {PGP_REVOCATION_COMPROMISED, "Key material has been compromised"}, + {PGP_REVOCATION_RETIRED, "Key is retired and no longer used"}, + {PGP_REVOCATION_NO_LONGER_VALID, "User ID information is no longer valid"}, + {0x00, NULL}, +}; + +pgp_key_t * +pgp_key_get_subkey(const pgp_key_t *key, rnp_key_store_t *store, size_t idx) +{ + try { + return rnp_key_store_get_key_by_fpr(store, key->get_subkey_fp(idx)); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return NULL; + } +} + +pgp_key_flags_t +pgp_pk_alg_capabilities(pgp_pubkey_alg_t alg) +{ + switch (alg) { + case PGP_PKA_RSA: + return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH | PGP_KF_ENCRYPT); + + case PGP_PKA_RSA_SIGN_ONLY: + // deprecated, but still usable + return PGP_KF_SIGN; + + case PGP_PKA_RSA_ENCRYPT_ONLY: + // deprecated, but still usable + return PGP_KF_ENCRYPT; + + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: /* deprecated */ + // These are no longer permitted per the RFC + return PGP_KF_NONE; + + case PGP_PKA_DSA: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH); + + case PGP_PKA_SM2: + return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH | PGP_KF_ENCRYPT); + + case PGP_PKA_ECDH: + case PGP_PKA_ELGAMAL: + return PGP_KF_ENCRYPT; + + default: + RNP_LOG("unknown pk alg: %d\n", alg); + return PGP_KF_NONE; + } +} + +bool +pgp_key_t::write_sec_pgp(pgp_dest_t & dst, + pgp_key_pkt_t & seckey, + const std::string &password, + rnp::RNG & rng) +{ + bool res = false; + pgp_pkt_type_t oldtag = seckey.tag; + + seckey.tag = type(); + if (encrypt_secret_key(&seckey, password.c_str(), rng)) { + goto done; + } + try { + seckey.write(dst); + res = !dst.werr; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } +done: + seckey.tag = oldtag; + return res; +} + +bool +pgp_key_t::write_sec_rawpkt(pgp_key_pkt_t & seckey, + const std::string & password, + rnp::SecurityContext &ctx) +{ + // encrypt+write the key in the appropriate format + try { + rnp::MemoryDest memdst; + switch (format) { + case PGP_KEY_STORE_GPG: + case PGP_KEY_STORE_KBX: + if (!write_sec_pgp(memdst.dst(), seckey, password, ctx.rng)) { + RNP_LOG("failed to write secret key"); + return false; + } + break; + case PGP_KEY_STORE_G10: + if (!g10_write_seckey(&memdst.dst(), &seckey, password.c_str(), ctx)) { + RNP_LOG("failed to write g10 secret key"); + return false; + } + break; + default: + RNP_LOG("invalid format"); + return false; + } + + rawpkt_ = pgp_rawpacket_t((uint8_t *) memdst.memory(), memdst.writeb(), type()); + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +static bool +update_sig_expiration(pgp_signature_t * dst, + const pgp_signature_t *src, + uint64_t create, + uint32_t expiry) +{ + try { + *dst = *src; + if (!expiry) { + dst->remove_subpkt(dst->get_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY)); + } else { + dst->set_key_expiration(expiry); + } + dst->set_creation(create); + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +bool +pgp_key_set_expiration(pgp_key_t * key, + pgp_key_t * seckey, + uint32_t expiry, + const pgp_password_provider_t &prov, + rnp::SecurityContext & ctx) +{ + if (!key->is_primary()) { + RNP_LOG("Not a primary key"); + return false; + } + + std::vector<pgp_sig_id_t> sigs; + /* update expiration for the latest direct-key signature and self-signature for each userid + */ + pgp_subsig_t *sig = key->latest_selfsig(PGP_UID_NONE); + if (sig) { + sigs.push_back(sig->sigid); + } + for (size_t uid = 0; uid < key->uid_count(); uid++) { + sig = key->latest_selfsig(uid); + if (sig) { + sigs.push_back(sig->sigid); + } + } + if (sigs.empty()) { + RNP_LOG("No valid self-signature(s)"); + return false; + } + + rnp::KeyLocker seclock(*seckey); + for (const auto &sigid : sigs) { + pgp_subsig_t &sig = key->get_sig(sigid); + /* update signature and re-sign it */ + if (!expiry && !sig.sig.has_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY)) { + continue; + } + + /* unlock secret key if needed */ + if (seckey->is_locked() && !seckey->unlock(prov)) { + RNP_LOG("Failed to unlock secret key"); + return false; + } + + pgp_signature_t newsig; + pgp_sig_id_t oldsigid = sigid; + if (!update_sig_expiration(&newsig, &sig.sig, ctx.time(), expiry)) { + return false; + } + try { + if (sig.is_cert()) { + if (sig.uid >= key->uid_count()) { + RNP_LOG("uid not found"); + return false; + } + seckey->sign_cert(key->pkt(), key->get_uid(sig.uid).pkt, newsig, ctx); + } else { + /* direct-key signature case */ + seckey->sign_direct(key->pkt(), newsig, ctx); + } + /* replace signature, first for secret key since it may be replaced in public */ + if (seckey->has_sig(oldsigid)) { + seckey->replace_sig(oldsigid, newsig); + } + if (key != seckey) { + key->replace_sig(oldsigid, newsig); + } + } catch (const std::exception &e) { + RNP_LOG("failed to calculate or add signature: %s", e.what()); + return false; + } + } + + if (!seckey->refresh_data(ctx)) { + RNP_LOG("Failed to refresh seckey data."); + return false; + } + if ((key != seckey) && !key->refresh_data(ctx)) { + RNP_LOG("Failed to refresh key data."); + return false; + } + return true; +} + +bool +pgp_subkey_set_expiration(pgp_key_t * sub, + pgp_key_t * primsec, + pgp_key_t * secsub, + uint32_t expiry, + const pgp_password_provider_t &prov, + rnp::SecurityContext & ctx) +{ + if (!sub->is_subkey()) { + RNP_LOG("Not a subkey"); + return false; + } + + /* find the latest valid subkey binding */ + pgp_subsig_t *subsig = sub->latest_binding(); + if (!subsig) { + RNP_LOG("No valid subkey binding"); + return false; + } + if (!expiry && !subsig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY)) { + return true; + } + + rnp::KeyLocker primlock(*primsec); + if (primsec->is_locked() && !primsec->unlock(prov)) { + RNP_LOG("Failed to unlock primary key"); + return false; + } + bool subsign = secsub->can_sign(); + rnp::KeyLocker sublock(*secsub); + if (subsign && secsub->is_locked() && !secsub->unlock(prov)) { + RNP_LOG("Failed to unlock subkey"); + return false; + } + + try { + /* update signature and re-sign */ + pgp_signature_t newsig; + pgp_sig_id_t oldsigid = subsig->sigid; + if (!update_sig_expiration(&newsig, &subsig->sig, ctx.time(), expiry)) { + return false; + } + primsec->sign_subkey_binding(*secsub, newsig, ctx); + /* replace signature, first for the secret key since it may be replaced in public */ + if (secsub->has_sig(oldsigid)) { + secsub->replace_sig(oldsigid, newsig); + if (!secsub->refresh_data(primsec, ctx)) { + return false; + } + } + if (sub == secsub) { + return true; + } + sub->replace_sig(oldsigid, newsig); + return sub->refresh_data(primsec, ctx); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +pgp_key_t * +find_suitable_key(pgp_op_t op, + pgp_key_t * key, + pgp_key_provider_t *key_provider, + bool no_primary) +{ + if (!key) { + return NULL; + } + bool secret = false; + switch (op) { + case PGP_OP_ENCRYPT: + break; + case PGP_OP_SIGN: + case PGP_OP_CERTIFY: + secret = true; + break; + default: + RNP_LOG("Unsupported operation: %d", (int) op); + return NULL; + } + /* Return if specified primary key fits our needs */ + if (!no_primary && key->usable_for(op)) { + return key; + } + /* Check for the case when we need to look up for a secret key */ + pgp_key_request_ctx_t ctx(op, secret, PGP_KEY_SEARCH_FINGERPRINT); + if (!no_primary && secret && key->is_public() && key->usable_for(op, true)) { + ctx.search.by.fingerprint = key->fp(); + pgp_key_t *sec = pgp_request_key(key_provider, &ctx); + if (sec && sec->usable_for(op)) { + return sec; + } + } + /* Now look up for subkeys */ + pgp_key_t *subkey = NULL; + for (auto &fp : key->subkey_fps()) { + ctx.search.by.fingerprint = fp; + pgp_key_t *cur = pgp_request_key(key_provider, &ctx); + if (!cur || !cur->usable_for(op)) { + continue; + } + if (!subkey || (cur->creation() > subkey->creation())) { + subkey = cur; + } + } + return subkey; +} + +pgp_hash_alg_t +pgp_hash_adjust_alg_to_key(pgp_hash_alg_t hash, const pgp_key_pkt_t *pubkey) +{ + if ((pubkey->alg != PGP_PKA_DSA) && (pubkey->alg != PGP_PKA_ECDSA)) { + return hash; + } + + pgp_hash_alg_t hash_min; + if (pubkey->alg == PGP_PKA_ECDSA) { + hash_min = ecdsa_get_min_hash(pubkey->material.ec.curve); + } else { + hash_min = dsa_get_min_hash(mpi_bits(&pubkey->material.dsa.q)); + } + + if (rnp::Hash::size(hash) < rnp::Hash::size(hash_min)) { + return hash_min; + } + return hash; +} + +static void +bytevec_append_uniq(std::vector<uint8_t> &vec, uint8_t val) +{ + if (std::find(vec.begin(), vec.end(), val) == vec.end()) { + vec.push_back(val); + } +} + +void +pgp_user_prefs_t::set_symm_algs(const std::vector<uint8_t> &algs) +{ + symm_algs = algs; +} + +void +pgp_user_prefs_t::add_symm_alg(pgp_symm_alg_t alg) +{ + bytevec_append_uniq(symm_algs, alg); +} + +void +pgp_user_prefs_t::set_hash_algs(const std::vector<uint8_t> &algs) +{ + hash_algs = algs; +} + +void +pgp_user_prefs_t::add_hash_alg(pgp_hash_alg_t alg) +{ + bytevec_append_uniq(hash_algs, alg); +} + +void +pgp_user_prefs_t::set_z_algs(const std::vector<uint8_t> &algs) +{ + z_algs = algs; +} + +void +pgp_user_prefs_t::add_z_alg(pgp_compression_type_t alg) +{ + bytevec_append_uniq(z_algs, alg); +} + +void +pgp_user_prefs_t::set_ks_prefs(const std::vector<uint8_t> &prefs) +{ + ks_prefs = prefs; +} + +void +pgp_user_prefs_t::add_ks_pref(pgp_key_server_prefs_t pref) +{ + bytevec_append_uniq(ks_prefs, pref); +} + +pgp_rawpacket_t::pgp_rawpacket_t(const pgp_signature_t &sig) +{ + rnp::MemoryDest dst; + sig.write(dst.dst()); + raw = dst.to_vector(); + tag = PGP_PKT_SIGNATURE; +} + +pgp_rawpacket_t::pgp_rawpacket_t(pgp_key_pkt_t &key) +{ + rnp::MemoryDest dst; + key.write(dst.dst()); + raw = dst.to_vector(); + tag = key.tag; +} + +pgp_rawpacket_t::pgp_rawpacket_t(const pgp_userid_pkt_t &uid) +{ + rnp::MemoryDest dst; + uid.write(dst.dst()); + raw = dst.to_vector(); + tag = uid.tag; +} + +void +pgp_rawpacket_t::write(pgp_dest_t &dst) const +{ + dst_write(&dst, raw.data(), raw.size()); +} + +void +pgp_validity_t::mark_valid() +{ + validated = true; + valid = true; + expired = false; +} + +void +pgp_validity_t::reset() +{ + validated = false; + valid = false; + expired = false; +} + +pgp_subsig_t::pgp_subsig_t(const pgp_signature_t &pkt) +{ + sig = pkt; + sigid = sig.get_id(); + if (sig.has_subpkt(PGP_SIG_SUBPKT_TRUST)) { + trustlevel = sig.trust_level(); + trustamount = sig.trust_amount(); + } + prefs.set_symm_algs(sig.preferred_symm_algs()); + prefs.set_hash_algs(sig.preferred_hash_algs()); + prefs.set_z_algs(sig.preferred_z_algs()); + + if (sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) { + key_flags = sig.key_flags(); + } + if (sig.has_subpkt(PGP_SIG_SUBPKT_KEYSERV_PREFS)) { + prefs.set_ks_prefs({sig.key_server_prefs()}); + } + if (sig.has_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV)) { + prefs.key_server = sig.key_server(); + } + /* add signature rawpacket */ + rawpkt = pgp_rawpacket_t(sig); +} + +bool +pgp_subsig_t::valid() const +{ + return validity.validated && validity.valid && !validity.expired; +} + +bool +pgp_subsig_t::validated() const +{ + return validity.validated; +} + +bool +pgp_subsig_t::is_cert() const +{ + pgp_sig_type_t type = sig.type(); + return (type == PGP_CERT_CASUAL) || (type == PGP_CERT_GENERIC) || + (type == PGP_CERT_PERSONA) || (type == PGP_CERT_POSITIVE); +} + +bool +pgp_subsig_t::expired(uint64_t at) const +{ + /* sig expiration: absence of subpkt or 0 means it never expires */ + uint64_t expiration = sig.expiration(); + if (!expiration) { + return false; + } + return expiration + sig.creation() < at; +} + +pgp_userid_t::pgp_userid_t(const pgp_userid_pkt_t &uidpkt) +{ + /* copy packet data */ + pkt = uidpkt; + rawpkt = pgp_rawpacket_t(uidpkt); + /* populate uid string */ + if (uidpkt.tag == PGP_PKT_USER_ID) { + str = std::string(uidpkt.uid, uidpkt.uid + uidpkt.uid_len); + } else { + str = "(photo)"; + } +} + +size_t +pgp_userid_t::sig_count() const +{ + return sigs_.size(); +} + +const pgp_sig_id_t & +pgp_userid_t::get_sig(size_t idx) const +{ + if (idx >= sigs_.size()) { + throw std::out_of_range("idx"); + } + return sigs_[idx]; +} + +bool +pgp_userid_t::has_sig(const pgp_sig_id_t &id) const +{ + return std::find(sigs_.begin(), sigs_.end(), id) != sigs_.end(); +} + +void +pgp_userid_t::add_sig(const pgp_sig_id_t &sig) +{ + sigs_.push_back(sig); +} + +void +pgp_userid_t::replace_sig(const pgp_sig_id_t &id, const pgp_sig_id_t &newsig) +{ + auto it = std::find(sigs_.begin(), sigs_.end(), id); + if (it == sigs_.end()) { + throw std::invalid_argument("id"); + } + *it = newsig; +} + +bool +pgp_userid_t::del_sig(const pgp_sig_id_t &id) +{ + auto it = std::find(sigs_.begin(), sigs_.end(), id); + if (it == sigs_.end()) { + return false; + } + sigs_.erase(it); + return true; +} + +void +pgp_userid_t::clear_sigs() +{ + sigs_.clear(); +} + +pgp_revoke_t::pgp_revoke_t(pgp_subsig_t &sig) +{ + uid = sig.uid; + sigid = sig.sigid; + if (!sig.sig.has_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON)) { + RNP_LOG("Warning: no revocation reason in the revocation"); + code = PGP_REVOCATION_NO_REASON; + } else { + code = sig.sig.revocation_code(); + reason = sig.sig.revocation_reason(); + } + if (reason.empty()) { + reason = id_str_pair::lookup(ss_rr_code_map, code); + } +} + +pgp_key_t::pgp_key_t(const pgp_key_pkt_t &keypkt) : pkt_(keypkt) +{ + if (!is_key_pkt(pkt_.tag) || !pkt_.material.alg) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (pgp_keyid(keyid_, pkt_) || pgp_fingerprint(fingerprint_, pkt_) || + !rnp_key_store_get_key_grip(&pkt_.material, grip_)) { + throw rnp::rnp_exception(RNP_ERROR_GENERIC); + } + + /* parse secret key if not encrypted */ + if (is_secret_key_pkt(pkt_.tag)) { + bool cleartext = pkt_.sec_protection.s2k.usage == PGP_S2KU_NONE; + if (cleartext && decrypt_secret_key(&pkt_, NULL)) { + RNP_LOG("failed to setup key fields"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + /* decryption resets validity */ + pkt_.material.validity = keypkt.material.validity; + } + /* add rawpacket */ + rawpkt_ = pgp_rawpacket_t(pkt_); + format = PGP_KEY_STORE_GPG; +} + +pgp_key_t::pgp_key_t(const pgp_key_pkt_t &pkt, pgp_key_t &primary) : pgp_key_t(pkt) +{ + primary.link_subkey_fp(*this); +} + +pgp_key_t::pgp_key_t(const pgp_key_t &src, bool pubonly) +{ + /* Do some checks for g10 keys */ + if (src.format == PGP_KEY_STORE_G10) { + if (pubonly) { + RNP_LOG("attempt to copy public part from g10 key"); + throw std::invalid_argument("pubonly"); + } + } + + if (pubonly) { + pkt_ = pgp_key_pkt_t(src.pkt_, true); + rawpkt_ = pgp_rawpacket_t(pkt_); + } else { + pkt_ = src.pkt_; + rawpkt_ = src.rawpkt_; + } + + uids_ = src.uids_; + sigs_ = src.sigs_; + sigs_map_ = src.sigs_map_; + keysigs_ = src.keysigs_; + subkey_fps_ = src.subkey_fps_; + primary_fp_set_ = src.primary_fp_set_; + primary_fp_ = src.primary_fp_; + expiration_ = src.expiration_; + flags_ = src.flags_; + keyid_ = src.keyid_; + fingerprint_ = src.fingerprint_; + grip_ = src.grip_; + uid0_ = src.uid0_; + uid0_set_ = src.uid0_set_; + revoked_ = src.revoked_; + revocation_ = src.revocation_; + format = src.format; + validity_ = src.validity_; + valid_till_ = src.valid_till_; +} + +pgp_key_t::pgp_key_t(const pgp_transferable_key_t &src) : pgp_key_t(src.key) +{ + /* add direct-key signatures */ + for (auto &sig : src.signatures) { + add_sig(sig); + } + + /* add userids and their signatures */ + for (auto &uid : src.userids) { + add_uid(uid); + } +} + +pgp_key_t::pgp_key_t(const pgp_transferable_subkey_t &src, pgp_key_t *primary) + : pgp_key_t(src.subkey) +{ + /* add subkey binding signatures */ + for (auto &sig : src.signatures) { + add_sig(sig); + } + + /* setup key grips if primary is available */ + if (primary) { + primary->link_subkey_fp(*this); + } +} + +size_t +pgp_key_t::sig_count() const +{ + return sigs_.size(); +} + +pgp_subsig_t & +pgp_key_t::get_sig(size_t idx) +{ + if (idx >= sigs_.size()) { + throw std::out_of_range("idx"); + } + return get_sig(sigs_[idx]); +} + +const pgp_subsig_t & +pgp_key_t::get_sig(size_t idx) const +{ + if (idx >= sigs_.size()) { + throw std::out_of_range("idx"); + } + return get_sig(sigs_[idx]); +} + +bool +pgp_key_t::has_sig(const pgp_sig_id_t &id) const +{ + return sigs_map_.count(id); +} + +pgp_subsig_t & +pgp_key_t::get_sig(const pgp_sig_id_t &id) +{ + if (!has_sig(id)) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return sigs_map_.at(id); +} + +const pgp_subsig_t & +pgp_key_t::get_sig(const pgp_sig_id_t &id) const +{ + if (!has_sig(id)) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return sigs_map_.at(id); +} + +pgp_subsig_t & +pgp_key_t::replace_sig(const pgp_sig_id_t &id, const pgp_signature_t &newsig) +{ + /* save oldsig's uid */ + size_t uid = get_sig(id).uid; + /* delete first old sig since we may have theoretically the same sigid */ + pgp_sig_id_t oldid = id; + sigs_map_.erase(oldid); + auto &res = sigs_map_.emplace(std::make_pair(newsig.get_id(), newsig)).first->second; + res.uid = uid; + auto it = std::find(sigs_.begin(), sigs_.end(), oldid); + if (it == sigs_.end()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + *it = res.sigid; + if (uid == PGP_UID_NONE) { + auto it = std::find(keysigs_.begin(), keysigs_.end(), oldid); + if (it == keysigs_.end()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + *it = res.sigid; + } else { + uids_[uid].replace_sig(oldid, res.sigid); + } + return res; +} + +pgp_subsig_t & +pgp_key_t::add_sig(const pgp_signature_t &sig, size_t uid) +{ + const pgp_sig_id_t sigid = sig.get_id(); + sigs_map_.erase(sigid); + pgp_subsig_t &res = sigs_map_.emplace(std::make_pair(sigid, sig)).first->second; + res.uid = uid; + sigs_.push_back(sigid); + if (uid == PGP_UID_NONE) { + keysigs_.push_back(sigid); + } else { + uids_[uid].add_sig(sigid); + } + return res; +} + +bool +pgp_key_t::del_sig(const pgp_sig_id_t &sigid) +{ + if (!has_sig(sigid)) { + return false; + } + uint32_t uid = get_sig(sigid).uid; + if (uid == PGP_UID_NONE) { + /* signature over the key itself */ + auto it = std::find(keysigs_.begin(), keysigs_.end(), sigid); + if (it != keysigs_.end()) { + keysigs_.erase(it); + } + } else if (uid < uids_.size()) { + /* userid-related signature */ + uids_[uid].del_sig(sigid); + } + auto it = std::find(sigs_.begin(), sigs_.end(), sigid); + if (it != sigs_.end()) { + sigs_.erase(it); + } + return sigs_map_.erase(sigid); +} + +size_t +pgp_key_t::del_sigs(const std::vector<pgp_sig_id_t> &sigs) +{ + /* delete actual signatures */ + size_t res = 0; + for (auto &sig : sigs) { + res += sigs_map_.erase(sig); + } + /* rebuild vectors with signatures order */ + keysigs_.clear(); + for (auto &uid : uids_) { + uid.clear_sigs(); + } + std::vector<pgp_sig_id_t> newsigs; + newsigs.reserve(sigs_map_.size()); + for (auto &sigid : sigs_) { + if (!sigs_map_.count(sigid)) { + continue; + } + newsigs.push_back(sigid); + uint32_t uid = get_sig(sigid).uid; + if (uid == PGP_UID_NONE) { + keysigs_.push_back(sigid); + } else { + uids_[uid].add_sig(sigid); + } + } + sigs_ = std::move(newsigs); + return res; +} + +size_t +pgp_key_t::keysig_count() const +{ + return keysigs_.size(); +} + +pgp_subsig_t & +pgp_key_t::get_keysig(size_t idx) +{ + if (idx >= keysigs_.size()) { + throw std::out_of_range("idx"); + } + return get_sig(keysigs_[idx]); +} + +size_t +pgp_key_t::uid_count() const +{ + return uids_.size(); +} + +pgp_userid_t & +pgp_key_t::get_uid(size_t idx) +{ + if (idx >= uids_.size()) { + throw std::out_of_range("idx"); + } + return uids_[idx]; +} + +const pgp_userid_t & +pgp_key_t::get_uid(size_t idx) const +{ + if (idx >= uids_.size()) { + throw std::out_of_range("idx"); + } + return uids_[idx]; +} + +bool +pgp_key_t::has_uid(const std::string &uidstr) const +{ + for (auto &userid : uids_) { + if (!userid.valid) { + continue; + } + if (userid.str == uidstr) { + return true; + } + } + return false; +} + +void +pgp_key_t::del_uid(size_t idx) +{ + if (idx >= uids_.size()) { + throw std::out_of_range("idx"); + } + + std::vector<pgp_sig_id_t> newsigs; + /* copy sigs which do not belong to uid */ + newsigs.reserve(sigs_.size()); + for (auto &id : sigs_) { + if (get_sig(id).uid == idx) { + sigs_map_.erase(id); + continue; + } + newsigs.push_back(id); + } + sigs_ = newsigs; + uids_.erase(uids_.begin() + idx); + /* update uids */ + if (idx == uids_.size()) { + return; + } + for (auto &sig : sigs_map_) { + if ((sig.second.uid == PGP_UID_NONE) || (sig.second.uid <= idx)) { + continue; + } + sig.second.uid--; + } +} + +bool +pgp_key_t::has_primary_uid() const +{ + return uid0_set_; +} + +uint32_t +pgp_key_t::get_primary_uid() const +{ + if (!uid0_set_) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return uid0_; +} + +pgp_userid_t & +pgp_key_t::add_uid(const pgp_transferable_userid_t &uid) +{ + /* construct userid */ + uids_.emplace_back(uid.uid); + /* add certifications */ + for (auto &sig : uid.signatures) { + add_sig(sig, uid_count() - 1); + } + return uids_.back(); +} + +bool +pgp_key_t::revoked() const +{ + return revoked_; +} + +const pgp_revoke_t & +pgp_key_t::revocation() const +{ + if (!revoked_) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return revocation_; +} + +void +pgp_key_t::clear_revokes() +{ + revoked_ = false; + revocation_ = {}; + for (auto &uid : uids_) { + uid.revoked = false; + uid.revocation = {}; + } +} + +const pgp_key_pkt_t & +pgp_key_t::pkt() const +{ + return pkt_; +} + +pgp_key_pkt_t & +pgp_key_t::pkt() +{ + return pkt_; +} + +void +pgp_key_t::set_pkt(const pgp_key_pkt_t &pkt) +{ + pkt_ = pkt; +} + +pgp_key_material_t & +pgp_key_t::material() +{ + return pkt_.material; +} + +pgp_pubkey_alg_t +pgp_key_t::alg() const +{ + return pkt_.alg; +} + +pgp_curve_t +pgp_key_t::curve() const +{ + switch (alg()) { + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + return pkt_.material.ec.curve; + default: + return PGP_CURVE_UNKNOWN; + } +} + +pgp_version_t +pgp_key_t::version() const +{ + return pkt().version; +} + +pgp_pkt_type_t +pgp_key_t::type() const +{ + return pkt().tag; +} + +bool +pgp_key_t::encrypted() const +{ + return is_secret() && !pkt().material.secret; +} + +uint8_t +pgp_key_t::flags() const +{ + return flags_; +} + +bool +pgp_key_t::can_sign() const +{ + return flags_ & PGP_KF_SIGN; +} + +bool +pgp_key_t::can_certify() const +{ + return flags_ & PGP_KF_CERTIFY; +} + +bool +pgp_key_t::can_encrypt() const +{ + return flags_ & PGP_KF_ENCRYPT; +} + +bool +pgp_key_t::has_secret() const +{ + if (!is_secret()) { + return false; + } + if ((format == PGP_KEY_STORE_GPG) && !pkt_.sec_len) { + return false; + } + if (pkt_.sec_protection.s2k.usage == PGP_S2KU_NONE) { + return true; + } + switch (pkt_.sec_protection.s2k.specifier) { + case PGP_S2KS_SIMPLE: + case PGP_S2KS_SALTED: + case PGP_S2KS_ITERATED_AND_SALTED: + return true; + default: + return false; + } +} + +bool +pgp_key_t::usable_for(pgp_op_t op, bool if_secret) const +{ + switch (op) { + case PGP_OP_ADD_SUBKEY: + return is_primary() && can_sign() && (if_secret || has_secret()); + case PGP_OP_SIGN: + return can_sign() && valid() && (if_secret || has_secret()); + case PGP_OP_CERTIFY: + return can_certify() && valid() && (if_secret || has_secret()); + case PGP_OP_DECRYPT: + return can_encrypt() && valid() && (if_secret || has_secret()); + case PGP_OP_UNLOCK: + case PGP_OP_PROTECT: + case PGP_OP_UNPROTECT: + return has_secret(); + case PGP_OP_VERIFY: + return can_sign() && valid(); + case PGP_OP_ADD_USERID: + return is_primary() && can_sign() && (if_secret || has_secret()); + case PGP_OP_ENCRYPT: + return can_encrypt() && valid(); + default: + return false; + } +} + +uint32_t +pgp_key_t::expiration() const +{ + if (pkt_.version >= 4) { + return expiration_; + } + /* too large value for pkt.v3_days may overflow uint32_t */ + if (pkt_.v3_days > (0xffffffffu / 86400)) { + return 0xffffffffu; + } + return (uint32_t) pkt_.v3_days * 86400; +} + +bool +pgp_key_t::expired() const +{ + return validity_.expired; +} + +uint32_t +pgp_key_t::creation() const +{ + return pkt_.creation_time; +} + +bool +pgp_key_t::is_public() const +{ + return is_public_key_pkt(pkt_.tag); +} + +bool +pgp_key_t::is_secret() const +{ + return is_secret_key_pkt(pkt_.tag); +} + +bool +pgp_key_t::is_primary() const +{ + return is_primary_key_pkt(pkt_.tag); +} + +bool +pgp_key_t::is_subkey() const +{ + return is_subkey_pkt(pkt_.tag); +} + +bool +pgp_key_t::is_locked() const +{ + if (!is_secret()) { + RNP_LOG("key is not a secret key"); + return false; + } + return encrypted(); +} + +bool +pgp_key_t::is_protected() const +{ + // sanity check + if (!is_secret()) { + RNP_LOG("Warning: this is not a secret key"); + } + return pkt_.sec_protection.s2k.usage != PGP_S2KU_NONE; +} + +bool +pgp_key_t::valid() const +{ + return validity_.validated && validity_.valid && !validity_.expired; +} + +bool +pgp_key_t::validated() const +{ + return validity_.validated; +} + +uint64_t +pgp_key_t::valid_till_common(bool expiry) const +{ + if (!validated()) { + return 0; + } + uint64_t till = expiration() ? (uint64_t) creation() + expiration() : UINT64_MAX; + if (valid()) { + return till; + } + if (revoked()) { + /* we should not believe to the compromised key at all */ + if (revocation_.code == PGP_REVOCATION_COMPROMISED) { + return 0; + } + const pgp_subsig_t &revsig = get_sig(revocation_.sigid); + if (revsig.sig.creation() > creation()) { + /* pick less time from revocation time and expiration time */ + return std::min((uint64_t) revsig.sig.creation(), till); + } + return 0; + } + /* if key is not marked as expired then it wasn't valid at all */ + return expiry ? till : 0; +} + +uint64_t +pgp_key_t::valid_till() const +{ + return valid_till_; +} + +bool +pgp_key_t::valid_at(uint64_t timestamp) const +{ + /* TODO: consider implementing more sophisticated checks, as key validity time could + * possibly be non-continuous */ + return (timestamp >= creation()) && timestamp && (timestamp <= valid_till()); +} + +const pgp_key_id_t & +pgp_key_t::keyid() const +{ + return keyid_; +} + +const pgp_fingerprint_t & +pgp_key_t::fp() const +{ + return fingerprint_; +} + +const pgp_key_grip_t & +pgp_key_t::grip() const +{ + return grip_; +} + +const pgp_fingerprint_t & +pgp_key_t::primary_fp() const +{ + if (!primary_fp_set_) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return primary_fp_; +} + +bool +pgp_key_t::has_primary_fp() const +{ + return primary_fp_set_; +} + +void +pgp_key_t::unset_primary_fp() +{ + primary_fp_set_ = false; + primary_fp_ = {}; +} + +void +pgp_key_t::link_subkey_fp(pgp_key_t &subkey) +{ + if (!is_primary() || !subkey.is_subkey()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + subkey.primary_fp_ = fp(); + subkey.primary_fp_set_ = true; + add_subkey_fp(subkey.fp()); +} + +void +pgp_key_t::add_subkey_fp(const pgp_fingerprint_t &fp) +{ + if (std::find(subkey_fps_.begin(), subkey_fps_.end(), fp) == subkey_fps_.end()) { + subkey_fps_.push_back(fp); + } +} + +size_t +pgp_key_t::subkey_count() const +{ + return subkey_fps_.size(); +} + +void +pgp_key_t::remove_subkey_fp(const pgp_fingerprint_t &fp) +{ + auto it = std::find(subkey_fps_.begin(), subkey_fps_.end(), fp); + if (it != subkey_fps_.end()) { + subkey_fps_.erase(it); + } +} + +const pgp_fingerprint_t & +pgp_key_t::get_subkey_fp(size_t idx) const +{ + return subkey_fps_[idx]; +} + +const std::vector<pgp_fingerprint_t> & +pgp_key_t::subkey_fps() const +{ + return subkey_fps_; +} + +size_t +pgp_key_t::rawpkt_count() const +{ + if (format == PGP_KEY_STORE_G10) { + return 1; + } + return 1 + uid_count() + sig_count(); +} + +pgp_rawpacket_t & +pgp_key_t::rawpkt() +{ + return rawpkt_; +} + +const pgp_rawpacket_t & +pgp_key_t::rawpkt() const +{ + return rawpkt_; +} + +void +pgp_key_t::set_rawpkt(const pgp_rawpacket_t &src) +{ + rawpkt_ = src; +} + +bool +pgp_key_t::unlock(const pgp_password_provider_t &provider, pgp_op_t op) +{ + // sanity checks + if (!usable_for(PGP_OP_UNLOCK)) { + return false; + } + // see if it's already unlocked + if (!is_locked()) { + return true; + } + + pgp_password_ctx_t ctx(op, this); + pgp_key_pkt_t * decrypted_seckey = pgp_decrypt_seckey(*this, provider, ctx); + if (!decrypted_seckey) { + return false; + } + + // this shouldn't really be necessary, but just in case + forget_secret_key_fields(&pkt_.material); + // copy the decrypted mpis into the pgp_key_t + pkt_.material = decrypted_seckey->material; + pkt_.material.secret = true; + delete decrypted_seckey; + return true; +} + +bool +pgp_key_t::lock() +{ + // sanity checks + if (!is_secret()) { + RNP_LOG("invalid args"); + return false; + } + + // see if it's already locked + if (is_locked()) { + return true; + } + + forget_secret_key_fields(&pkt_.material); + return true; +} + +bool +pgp_key_t::protect(const rnp_key_protection_params_t &protection, + const pgp_password_provider_t & password_provider, + rnp::SecurityContext & sctx) +{ + pgp_password_ctx_t ctx(PGP_OP_PROTECT, this); + + // ask the provider for a password + rnp::secure_array<char, MAX_PASSWORD_LENGTH> password; + if (!pgp_request_password(&password_provider, &ctx, password.data(), password.size())) { + return false; + } + return protect(pkt_, protection, password.data(), sctx); +} + +bool +pgp_key_t::protect(pgp_key_pkt_t & decrypted, + const rnp_key_protection_params_t &protection, + const std::string & new_password, + rnp::SecurityContext & ctx) +{ + if (!is_secret()) { + RNP_LOG("Warning: this is not a secret key"); + return false; + } + bool ownpkt = &decrypted == &pkt_; + if (!decrypted.material.secret) { + RNP_LOG("Decrypted secret key must be provided"); + return false; + } + + /* force encrypted-and-hashed and iterated-and-salted as it's the only method we support*/ + pkt_.sec_protection.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED; + pkt_.sec_protection.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED; + /* use default values where needed */ + pkt_.sec_protection.symm_alg = + protection.symm_alg ? protection.symm_alg : DEFAULT_PGP_SYMM_ALG; + pkt_.sec_protection.cipher_mode = + protection.cipher_mode ? protection.cipher_mode : DEFAULT_PGP_CIPHER_MODE; + pkt_.sec_protection.s2k.hash_alg = + protection.hash_alg ? protection.hash_alg : DEFAULT_PGP_HASH_ALG; + auto iter = protection.iterations; + if (!iter) { + iter = ctx.s2k_iterations(pkt_.sec_protection.s2k.hash_alg); + } + pkt_.sec_protection.s2k.iterations = pgp_s2k_round_iterations(iter); + if (!ownpkt) { + /* decrypted is assumed to be temporary variable so we may modify it */ + decrypted.sec_protection = pkt_.sec_protection; + } + + /* write the protected key to raw packet */ + return write_sec_rawpkt(decrypted, new_password, ctx); +} + +bool +pgp_key_t::unprotect(const pgp_password_provider_t &password_provider, + rnp::SecurityContext & secctx) +{ + /* sanity check */ + if (!is_secret()) { + RNP_LOG("Warning: this is not a secret key"); + return false; + } + /* already unprotected */ + if (!is_protected()) { + return true; + } + /* simple case */ + if (!encrypted()) { + pkt_.sec_protection.s2k.usage = PGP_S2KU_NONE; + return write_sec_rawpkt(pkt_, "", secctx); + } + + pgp_password_ctx_t ctx(PGP_OP_UNPROTECT, this); + + pgp_key_pkt_t *decrypted_seckey = pgp_decrypt_seckey(*this, password_provider, ctx); + if (!decrypted_seckey) { + return false; + } + decrypted_seckey->sec_protection.s2k.usage = PGP_S2KU_NONE; + if (!write_sec_rawpkt(*decrypted_seckey, "", secctx)) { + delete decrypted_seckey; + return false; + } + pkt_ = std::move(*decrypted_seckey); + /* current logic is that unprotected key should be additionally unlocked */ + forget_secret_key_fields(&pkt_.material); + delete decrypted_seckey; + return true; +} + +void +pgp_key_t::write(pgp_dest_t &dst) const +{ + /* write key rawpacket */ + rawpkt_.write(dst); + + if (format == PGP_KEY_STORE_G10) { + return; + } + + /* write signatures on key */ + for (auto &sigid : keysigs_) { + get_sig(sigid).rawpkt.write(dst); + } + + /* write uids and their signatures */ + for (const auto &uid : uids_) { + uid.rawpkt.write(dst); + for (size_t idx = 0; idx < uid.sig_count(); idx++) { + get_sig(uid.get_sig(idx)).rawpkt.write(dst); + } + } +} + +void +pgp_key_t::write_xfer(pgp_dest_t &dst, const rnp_key_store_t *keyring) const +{ + write(dst); + if (dst.werr) { + RNP_LOG("Failed to export primary key"); + return; + } + + if (!keyring) { + return; + } + + // Export subkeys + for (auto &fp : subkey_fps_) { + const pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(keyring, fp); + if (!subkey) { + char fphex[PGP_FINGERPRINT_SIZE * 2 + 1] = {0}; + rnp::hex_encode( + fp.fingerprint, fp.length, fphex, sizeof(fphex), rnp::HEX_LOWERCASE); + RNP_LOG("Warning! Subkey %s not found.", fphex); + continue; + } + subkey->write(dst); + if (dst.werr) { + RNP_LOG("Error occurred when exporting a subkey"); + return; + } + } +} + +bool +pgp_key_t::write_autocrypt(pgp_dest_t &dst, pgp_key_t &sub, uint32_t uid) +{ + pgp_subsig_t *cert = latest_uid_selfcert(uid); + if (!cert) { + RNP_LOG("No valid uid certification"); + return false; + } + pgp_subsig_t *binding = sub.latest_binding(); + if (!binding) { + RNP_LOG("No valid binding for subkey"); + return false; + } + if (is_secret() || sub.is_secret()) { + RNP_LOG("Public key required"); + return false; + } + + try { + /* write all or nothing */ + rnp::MemoryDest memdst; + pkt().write(memdst.dst()); + get_uid(uid).pkt.write(memdst.dst()); + cert->sig.write(memdst.dst()); + sub.pkt().write(memdst.dst()); + binding->sig.write(memdst.dst()); + dst_write(&dst, memdst.memory(), memdst.writeb()); + return !dst.werr; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +/* look only for primary userids */ +#define PGP_UID_PRIMARY ((uint32_t) -2) +/* look for any uid, except PGP_UID_NONE) */ +#define PGP_UID_ANY ((uint32_t) -3) + +pgp_subsig_t * +pgp_key_t::latest_selfsig(uint32_t uid) +{ + uint32_t latest = 0; + pgp_subsig_t *res = nullptr; + + for (auto &sigid : sigs_) { + auto &sig = get_sig(sigid); + if (!sig.valid()) { + continue; + } + bool skip = false; + switch (uid) { + case PGP_UID_NONE: + skip = (sig.uid != PGP_UID_NONE) || !is_direct_self(sig); + break; + case PGP_UID_PRIMARY: { + pgp_sig_subpkt_t *subpkt = sig.sig.get_subpkt(PGP_SIG_SUBPKT_PRIMARY_USER_ID); + skip = !is_self_cert(sig) || !subpkt || !subpkt->fields.primary_uid || + (sig.uid == PGP_UID_NONE); + break; + } + case PGP_UID_ANY: + skip = !is_self_cert(sig) || (sig.uid == PGP_UID_NONE); + break; + default: + skip = (sig.uid != uid) || !is_self_cert(sig); + break; + } + if (skip) { + continue; + } + + uint32_t creation = sig.sig.creation(); + if (creation >= latest) { + latest = creation; + res = &sig; + } + } + + /* if there is later self-sig for the same uid without primary flag, then drop res */ + if ((uid == PGP_UID_PRIMARY) && res) { + pgp_subsig_t *overres = latest_selfsig(res->uid); + if (overres && (overres->sig.creation() > res->sig.creation())) { + res = nullptr; + } + } + return res; +} + +pgp_subsig_t * +pgp_key_t::latest_binding(bool validated) +{ + uint32_t latest = 0; + pgp_subsig_t *res = NULL; + + for (auto &sigid : sigs_) { + auto &sig = get_sig(sigid); + if (validated && !sig.valid()) { + continue; + } + if (!is_binding(sig)) { + continue; + } + + uint32_t creation = sig.sig.creation(); + if (creation >= latest) { + latest = creation; + res = &sig; + } + } + return res; +} + +pgp_subsig_t * +pgp_key_t::latest_uid_selfcert(uint32_t uid) +{ + uint32_t latest = 0; + pgp_subsig_t *res = NULL; + + if (uid >= uids_.size()) { + return NULL; + } + + for (size_t idx = 0; idx < uids_[uid].sig_count(); idx++) { + auto &sig = get_sig(uids_[uid].get_sig(idx)); + if (!sig.valid() || (sig.uid != uid)) { + continue; + } + if (!is_self_cert(sig)) { + continue; + } + + uint32_t creation = sig.sig.creation(); + if (creation >= latest) { + latest = creation; + res = &sig; + } + } + return res; +} + +bool +pgp_key_t::is_signer(const pgp_subsig_t &sig) const +{ + /* if we have fingerprint let's check it */ + if (sig.sig.has_keyfp()) { + return sig.sig.keyfp() == fp(); + } + if (!sig.sig.has_keyid()) { + return false; + } + return keyid() == sig.sig.keyid(); +} + +bool +pgp_key_t::expired_with(const pgp_subsig_t &sig, uint64_t at) const +{ + /* key expiration: absence of subpkt or 0 means it never expires */ + uint64_t expiration = sig.sig.key_expiration(); + if (!expiration) { + return false; + } + return expiration + creation() < at; +} + +bool +pgp_key_t::is_self_cert(const pgp_subsig_t &sig) const +{ + return is_primary() && sig.is_cert() && is_signer(sig); +} + +bool +pgp_key_t::is_direct_self(const pgp_subsig_t &sig) const +{ + return is_primary() && (sig.sig.type() == PGP_SIG_DIRECT) && is_signer(sig); +} + +bool +pgp_key_t::is_revocation(const pgp_subsig_t &sig) const +{ + return is_primary() ? (sig.sig.type() == PGP_SIG_REV_KEY) : + (sig.sig.type() == PGP_SIG_REV_SUBKEY); +} + +bool +pgp_key_t::is_uid_revocation(const pgp_subsig_t &sig) const +{ + return is_primary() && (sig.sig.type() == PGP_SIG_REV_CERT); +} + +bool +pgp_key_t::is_binding(const pgp_subsig_t &sig) const +{ + return is_subkey() && (sig.sig.type() == PGP_SIG_SUBKEY); +} + +void +pgp_key_t::validate_sig(const pgp_key_t & key, + pgp_subsig_t & sig, + const rnp::SecurityContext &ctx) const noexcept +{ + sig.validity.reset(); + + pgp_signature_info_t sinfo = {}; + sinfo.sig = &sig.sig; + sinfo.signer_valid = true; + if (key.is_self_cert(sig) || key.is_binding(sig)) { + sinfo.ignore_expiry = true; + } + + pgp_sig_type_t stype = sig.sig.type(); + try { + switch (stype) { + case PGP_SIG_BINARY: + case PGP_SIG_TEXT: + case PGP_SIG_STANDALONE: + case PGP_SIG_PRIMARY: + RNP_LOG("Invalid key signature type: %d", (int) stype); + return; + case PGP_CERT_GENERIC: + case PGP_CERT_PERSONA: + case PGP_CERT_CASUAL: + case PGP_CERT_POSITIVE: + case PGP_SIG_REV_CERT: { + if (sig.uid >= key.uid_count()) { + RNP_LOG("Userid not found"); + return; + } + validate_cert(sinfo, key.pkt(), key.get_uid(sig.uid).pkt, ctx); + break; + } + case PGP_SIG_SUBKEY: + if (!is_signer(sig)) { + RNP_LOG("Invalid subkey binding's signer."); + return; + } + validate_binding(sinfo, key, ctx); + break; + case PGP_SIG_DIRECT: + case PGP_SIG_REV_KEY: + validate_direct(sinfo, ctx); + break; + case PGP_SIG_REV_SUBKEY: + if (!is_signer(sig)) { + RNP_LOG("Invalid subkey revocation's signer."); + return; + } + validate_sub_rev(sinfo, key.pkt(), ctx); + break; + default: + RNP_LOG("Unsupported key signature type: %d", (int) stype); + return; + } + } catch (const std::exception &e) { + RNP_LOG("Key signature validation failed: %s", e.what()); + } + + sig.validity.validated = true; + sig.validity.valid = sinfo.valid; + /* revocation signature cannot expire */ + if ((stype != PGP_SIG_REV_KEY) && (stype != PGP_SIG_REV_SUBKEY) && + (stype != PGP_SIG_REV_CERT)) { + sig.validity.expired = sinfo.expired; + } +} + +void +pgp_key_t::validate_sig(pgp_signature_info_t & sinfo, + rnp::Hash & hash, + const rnp::SecurityContext &ctx) const noexcept +{ + sinfo.no_signer = false; + sinfo.valid = false; + sinfo.expired = false; + + /* Validate signature itself */ + if (sinfo.signer_valid || valid_at(sinfo.sig->creation())) { + sinfo.valid = !signature_validate(*sinfo.sig, pkt_.material, hash, ctx); + } else { + sinfo.valid = false; + RNP_LOG("invalid or untrusted key"); + } + + /* Check signature's expiration time */ + uint32_t now = ctx.time(); + uint32_t create = sinfo.sig->creation(); + uint32_t expiry = sinfo.sig->expiration(); + if (create > now) { + /* signature created later then now */ + RNP_LOG("signature created %d seconds in future", (int) (create - now)); + sinfo.expired = true; + } + if (create && expiry && (create + expiry < now)) { + /* signature expired */ + RNP_LOG("signature expired"); + sinfo.expired = true; + } + + /* check key creation time vs signature creation */ + if (creation() > create) { + RNP_LOG("key is newer than signature"); + sinfo.valid = false; + } + + /* check whether key was not expired when sig created */ + if (!sinfo.ignore_expiry && expiration() && (creation() + expiration() < create)) { + RNP_LOG("signature made after key expiration"); + sinfo.valid = false; + } + + /* Check signer's fingerprint */ + if (sinfo.sig->has_keyfp() && (sinfo.sig->keyfp() != fp())) { + RNP_LOG("issuer fingerprint doesn't match signer's one"); + sinfo.valid = false; + } + + /* Check for unknown critical notations */ + for (auto &subpkt : sinfo.sig->subpkts) { + if (!subpkt.critical || (subpkt.type != PGP_SIG_SUBPKT_NOTATION_DATA)) { + continue; + } + std::string name(subpkt.fields.notation.name, + subpkt.fields.notation.name + subpkt.fields.notation.nlen); + RNP_LOG("unknown critical notation: %s", name.c_str()); + sinfo.valid = false; + } +} + +void +pgp_key_t::validate_cert(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & key, + const pgp_userid_pkt_t & uid, + const rnp::SecurityContext &ctx) const +{ + auto hash = signature_hash_certification(*sinfo.sig, key, uid); + validate_sig(sinfo, *hash, ctx); +} + +void +pgp_key_t::validate_binding(pgp_signature_info_t & sinfo, + const pgp_key_t & subkey, + const rnp::SecurityContext &ctx) const +{ + if (!is_primary() || !subkey.is_subkey()) { + RNP_LOG("Invalid binding signature key type(s)"); + sinfo.valid = false; + return; + } + auto hash = signature_hash_binding(*sinfo.sig, pkt(), subkey.pkt()); + validate_sig(sinfo, *hash, ctx); + if (!sinfo.valid || !(sinfo.sig->key_flags() & PGP_KF_SIGN)) { + return; + } + + /* check primary key binding signature if any */ + sinfo.valid = false; + pgp_sig_subpkt_t *subpkt = sinfo.sig->get_subpkt(PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE, false); + if (!subpkt) { + RNP_LOG("error! no primary key binding signature"); + return; + } + if (!subpkt->parsed) { + RNP_LOG("invalid embedded signature subpacket"); + return; + } + if (subpkt->fields.sig->type() != PGP_SIG_PRIMARY) { + RNP_LOG("invalid primary key binding signature"); + return; + } + if (subpkt->fields.sig->version < PGP_V4) { + RNP_LOG("invalid primary key binding signature version"); + return; + } + + hash = signature_hash_binding(*subpkt->fields.sig, pkt(), subkey.pkt()); + pgp_signature_info_t bindinfo = {}; + bindinfo.sig = subpkt->fields.sig; + bindinfo.signer_valid = true; + bindinfo.ignore_expiry = true; + subkey.validate_sig(bindinfo, *hash, ctx); + sinfo.valid = bindinfo.valid && !bindinfo.expired; +} + +void +pgp_key_t::validate_sub_rev(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & subkey, + const rnp::SecurityContext &ctx) const +{ + auto hash = signature_hash_binding(*sinfo.sig, pkt(), subkey); + validate_sig(sinfo, *hash, ctx); +} + +void +pgp_key_t::validate_direct(pgp_signature_info_t &sinfo, const rnp::SecurityContext &ctx) const +{ + auto hash = signature_hash_direct(*sinfo.sig, pkt()); + validate_sig(sinfo, *hash, ctx); +} + +void +pgp_key_t::validate_self_signatures(const rnp::SecurityContext &ctx) +{ + for (auto &sigid : sigs_) { + pgp_subsig_t &sig = get_sig(sigid); + if (sig.validity.validated) { + continue; + } + + if (is_direct_self(sig) || is_self_cert(sig) || is_uid_revocation(sig) || + is_revocation(sig)) { + validate_sig(*this, sig, ctx); + } + } +} + +void +pgp_key_t::validate_self_signatures(pgp_key_t &primary, const rnp::SecurityContext &ctx) +{ + for (auto &sigid : sigs_) { + pgp_subsig_t &sig = get_sig(sigid); + if (sig.validity.validated) { + continue; + } + + if (is_binding(sig) || is_revocation(sig)) { + primary.validate_sig(*this, sig, ctx); + } + } +} + +void +pgp_key_t::validate_primary(rnp_key_store_t &keyring) +{ + /* validate signatures if needed */ + validate_self_signatures(keyring.secctx); + + /* consider public key as valid on this level if it is not expired and has at least one + * valid self-signature, and is not revoked */ + validity_.reset(); + validity_.validated = true; + bool has_cert = false; + bool has_expired = false; + /* check whether key is revoked */ + for (auto &sigid : sigs_) { + pgp_subsig_t &sig = get_sig(sigid); + if (!sig.valid()) { + continue; + } + if (is_revocation(sig)) { + return; + } + } + /* if we have direct-key signature, then it has higher priority for expiration check */ + uint64_t now = keyring.secctx.time(); + pgp_subsig_t *dirsig = latest_selfsig(PGP_UID_NONE); + if (dirsig) { + has_expired = expired_with(*dirsig, now); + has_cert = !has_expired; + } + /* if we have primary uid and it is more restrictive, then use it as well */ + pgp_subsig_t *prisig = NULL; + if (!has_expired && (prisig = latest_selfsig(PGP_UID_PRIMARY))) { + has_expired = expired_with(*prisig, now); + has_cert = !has_expired; + } + /* if we don't have direct-key sig and primary uid, use the latest self-cert */ + pgp_subsig_t *latest = NULL; + if (!dirsig && !prisig && (latest = latest_selfsig(PGP_UID_ANY))) { + has_expired = expired_with(*latest, now); + has_cert = !has_expired; + } + + /* we have at least one non-expiring key self-signature */ + if (has_cert) { + validity_.valid = true; + return; + } + /* we have valid self-signature which expires key */ + if (has_expired) { + validity_.expired = true; + return; + } + + /* let's check whether key has at least one valid subkey binding */ + for (size_t i = 0; i < subkey_count(); i++) { + pgp_key_t *sub = pgp_key_get_subkey(this, &keyring, i); + if (!sub) { + continue; + } + sub->validate_self_signatures(*this, keyring.secctx); + pgp_subsig_t *sig = sub->latest_binding(); + if (!sig) { + continue; + } + /* check whether subkey is expired - then do not mark key as valid */ + if (sub->expired_with(*sig, now)) { + continue; + } + validity_.valid = true; + return; + } +} + +void +pgp_key_t::validate_subkey(pgp_key_t *primary, const rnp::SecurityContext &ctx) +{ + /* consider subkey as valid on this level if it has valid primary key, has at least one + * non-expired binding signature, and is not revoked. */ + validity_.reset(); + validity_.validated = true; + if (!primary || (!primary->valid() && !primary->expired())) { + return; + } + /* validate signatures if needed */ + validate_self_signatures(*primary, ctx); + + bool has_binding = false; + bool has_expired = false; + for (auto &sigid : sigs_) { + pgp_subsig_t &sig = get_sig(sigid); + if (!sig.valid()) { + continue; + } + + if (is_binding(sig) && !has_binding) { + /* check whether subkey is expired */ + if (expired_with(sig, ctx.time())) { + has_expired = true; + continue; + } + has_binding = true; + } else if (is_revocation(sig)) { + return; + } + } + validity_.valid = has_binding && primary->valid(); + if (!validity_.valid) { + validity_.expired = has_expired; + } +} + +void +pgp_key_t::validate(rnp_key_store_t &keyring) +{ + validity_.reset(); + if (!is_subkey()) { + validate_primary(keyring); + } else { + pgp_key_t *primary = NULL; + if (has_primary_fp()) { + primary = rnp_key_store_get_key_by_fpr(&keyring, primary_fp()); + } + validate_subkey(primary, keyring.secctx); + } +} + +void +pgp_key_t::revalidate(rnp_key_store_t &keyring) +{ + if (is_subkey()) { + pgp_key_t *primary = rnp_key_store_get_primary_key(&keyring, this); + if (primary) { + primary->revalidate(keyring); + } else { + validate_subkey(NULL, keyring.secctx); + } + return; + } + + validate(keyring); + if (!refresh_data(keyring.secctx)) { + RNP_LOG("Failed to refresh key data"); + } + /* validate/re-validate all subkeys as well */ + for (auto &fp : subkey_fps_) { + pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(&keyring, fp); + if (subkey) { + subkey->validate_subkey(this, keyring.secctx); + if (!subkey->refresh_data(this, keyring.secctx)) { + RNP_LOG("Failed to refresh subkey data"); + } + } + } +} + +void +pgp_key_t::mark_valid() +{ + validity_.mark_valid(); + for (size_t i = 0; i < sig_count(); i++) { + get_sig(i).validity.mark_valid(); + } +} + +void +pgp_key_t::sign_init(pgp_signature_t &sig, pgp_hash_alg_t hash, uint64_t creation) const +{ + sig.version = PGP_V4; + sig.halg = pgp_hash_adjust_alg_to_key(hash, &pkt_); + sig.palg = alg(); + sig.set_keyfp(fp()); + sig.set_creation(creation); + sig.set_keyid(keyid()); +} + +void +pgp_key_t::sign_cert(const pgp_key_pkt_t & key, + const pgp_userid_pkt_t &uid, + pgp_signature_t & sig, + rnp::SecurityContext & ctx) +{ + sig.fill_hashed_data(); + auto hash = signature_hash_certification(sig, key, uid); + signature_calculate(sig, pkt_.material, *hash, ctx); +} + +void +pgp_key_t::sign_direct(const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx) +{ + sig.fill_hashed_data(); + auto hash = signature_hash_direct(sig, key); + signature_calculate(sig, pkt_.material, *hash, ctx); +} + +void +pgp_key_t::sign_binding(const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx) +{ + sig.fill_hashed_data(); + auto hash = is_primary() ? signature_hash_binding(sig, pkt(), key) : + signature_hash_binding(sig, key, pkt()); + signature_calculate(sig, pkt_.material, *hash, ctx); +} + +void +pgp_key_t::gen_revocation(const pgp_revoke_t & revoke, + pgp_hash_alg_t hash, + const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx) +{ + sign_init(sig, hash, ctx.time()); + sig.set_type(is_primary_key_pkt(key.tag) ? PGP_SIG_REV_KEY : PGP_SIG_REV_SUBKEY); + sig.set_revocation_reason(revoke.code, revoke.reason); + + if (is_primary_key_pkt(key.tag)) { + sign_direct(key, sig, ctx); + } else { + sign_binding(key, sig, ctx); + } +} + +void +pgp_key_t::sign_subkey_binding(pgp_key_t & sub, + pgp_signature_t & sig, + rnp::SecurityContext &ctx, + bool subsign) +{ + if (!is_primary()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + sign_binding(sub.pkt(), sig, ctx); + /* add primary key binding subpacket if requested */ + if (subsign) { + pgp_signature_t embsig; + sub.sign_init(embsig, sig.halg, ctx.time()); + embsig.set_type(PGP_SIG_PRIMARY); + sub.sign_binding(pkt(), embsig, ctx); + sig.set_embedded_sig(embsig); + } +} + +void +pgp_key_t::add_uid_cert(rnp_selfsig_cert_info_t &cert, + pgp_hash_alg_t hash, + rnp::SecurityContext & ctx, + pgp_key_t * pubkey) +{ + if (cert.userid.empty()) { + /* todo: why not to allow empty uid? */ + RNP_LOG("wrong parameters"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + // userids are only valid for primary keys, not subkeys + if (!is_primary()) { + RNP_LOG("cannot add a userid to a subkey"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + // see if the key already has this userid + if (has_uid(cert.userid)) { + RNP_LOG("key already has this userid"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + // this isn't really valid for this format + if (format == PGP_KEY_STORE_G10) { + RNP_LOG("Unsupported key store type"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + // We only support modifying v4 and newer keys + if (pkt().version < PGP_V4) { + RNP_LOG("adding a userid to V2/V3 key is not supported"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + /* TODO: if key has at least one uid then has_primary_uid() will be always true! */ + if (has_primary_uid() && cert.primary) { + RNP_LOG("changing the primary userid is not supported"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* Fill the transferable userid */ + pgp_userid_pkt_t uid; + pgp_signature_t sig; + sign_init(sig, hash, ctx.time()); + cert.populate(uid, sig); + try { + sign_cert(pkt_, uid, sig, ctx); + } catch (const std::exception &e) { + RNP_LOG("Failed to certify: %s", e.what()); + throw; + } + /* add uid and signature to the key and pubkey, if non-NULL */ + uids_.emplace_back(uid); + add_sig(sig, uid_count() - 1); + refresh_data(ctx); + if (!pubkey) { + return; + } + pubkey->uids_.emplace_back(uid); + pubkey->add_sig(sig, pubkey->uid_count() - 1); + pubkey->refresh_data(ctx); +} + +void +pgp_key_t::add_sub_binding(pgp_key_t & subsec, + pgp_key_t & subpub, + const rnp_selfsig_binding_info_t &binding, + pgp_hash_alg_t hash, + rnp::SecurityContext & ctx) +{ + if (!is_primary()) { + RNP_LOG("must be called on primary key"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* populate signature */ + pgp_signature_t sig; + sign_init(sig, hash, ctx.time()); + sig.set_type(PGP_SIG_SUBKEY); + if (binding.key_expiration) { + sig.set_key_expiration(binding.key_expiration); + } + if (binding.key_flags) { + sig.set_key_flags(binding.key_flags); + } + /* calculate binding */ + pgp_key_flags_t realkf = (pgp_key_flags_t) binding.key_flags; + if (!realkf) { + realkf = pgp_pk_alg_capabilities(subsec.alg()); + } + sign_subkey_binding(subsec, sig, ctx, realkf & PGP_KF_SIGN); + /* add to the secret and public key */ + subsec.add_sig(sig); + subpub.add_sig(sig); +} + +bool +pgp_key_t::refresh_data(const rnp::SecurityContext &ctx) +{ + if (!is_primary()) { + RNP_LOG("key must be primary"); + return false; + } + /* validate self-signatures if not done yet */ + validate_self_signatures(ctx); + /* key expiration */ + expiration_ = 0; + /* if we have direct-key signature, then it has higher priority */ + pgp_subsig_t *dirsig = latest_selfsig(PGP_UID_NONE); + if (dirsig) { + expiration_ = dirsig->sig.key_expiration(); + } + /* if we have primary uid and it is more restrictive, then use it as well */ + pgp_subsig_t *prisig = latest_selfsig(PGP_UID_PRIMARY); + if (prisig && prisig->sig.key_expiration() && + (!expiration_ || (prisig->sig.key_expiration() < expiration_))) { + expiration_ = prisig->sig.key_expiration(); + } + /* if we don't have direct-key sig and primary uid, use the latest self-cert */ + pgp_subsig_t *latest = latest_selfsig(PGP_UID_ANY); + if (!dirsig && !prisig && latest) { + expiration_ = latest->sig.key_expiration(); + } + /* key flags: check in direct-key sig first, then primary uid, and then latest */ + if (dirsig && dirsig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) { + flags_ = dirsig->key_flags; + } else if (prisig && prisig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) { + flags_ = prisig->key_flags; + } else if (latest && latest->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) { + flags_ = latest->key_flags; + } else { + flags_ = pgp_pk_alg_capabilities(alg()); + } + /* revocation(s) */ + clear_revokes(); + for (size_t i = 0; i < sig_count(); i++) { + pgp_subsig_t &sig = get_sig(i); + if (!sig.valid()) { + continue; + } + try { + if (is_revocation(sig)) { + if (revoked_) { + continue; + } + revoked_ = true; + revocation_ = pgp_revoke_t(sig); + } else if (is_uid_revocation(sig)) { + if (sig.uid >= uid_count()) { + RNP_LOG("Invalid uid index"); + continue; + } + pgp_userid_t &uid = get_uid(sig.uid); + if (uid.revoked) { + continue; + } + uid.revoked = true; + uid.revocation = pgp_revoke_t(sig); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + } + /* valid till */ + valid_till_ = valid_till_common(expired()); + /* userid validities */ + for (size_t i = 0; i < uid_count(); i++) { + get_uid(i).valid = false; + } + for (size_t i = 0; i < sig_count(); i++) { + pgp_subsig_t &sig = get_sig(i); + /* consider userid as valid if it has at least one non-expired self-sig */ + if (!sig.valid() || !sig.is_cert() || !is_signer(sig) || sig.expired(ctx.time())) { + continue; + } + if (sig.uid >= uid_count()) { + continue; + } + get_uid(sig.uid).valid = true; + } + /* check whether uid is revoked */ + for (size_t i = 0; i < uid_count(); i++) { + pgp_userid_t &uid = get_uid(i); + if (uid.revoked) { + uid.valid = false; + } + } + /* primary userid: use latest one which is not overridden by later non-primary selfsig */ + uid0_set_ = false; + if (prisig && get_uid(prisig->uid).valid) { + uid0_ = prisig->uid; + uid0_set_ = true; + } + return true; +} + +bool +pgp_key_t::refresh_data(pgp_key_t *primary, const rnp::SecurityContext &ctx) +{ + /* validate self-signatures if not done yet */ + if (primary) { + validate_self_signatures(*primary, ctx); + } + pgp_subsig_t *sig = latest_binding(primary); + /* subkey expiration */ + expiration_ = sig ? sig->sig.key_expiration() : 0; + /* subkey flags */ + if (sig && sig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) { + flags_ = sig->key_flags; + } else { + flags_ = pgp_pk_alg_capabilities(alg()); + } + /* revocation */ + clear_revokes(); + for (size_t i = 0; i < sig_count(); i++) { + pgp_subsig_t &sig = get_sig(i); + if (!sig.valid() || !is_revocation(sig)) { + continue; + } + revoked_ = true; + try { + revocation_ = pgp_revoke_t(sig); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + break; + } + /* valid till */ + if (primary) { + valid_till_ = + std::min(primary->valid_till(), valid_till_common(expired() || primary->expired())); + } else { + valid_till_ = valid_till_common(expired()); + } + return true; +} + +void +pgp_key_t::merge_validity(const pgp_validity_t &src) +{ + validity_.valid = validity_.valid && src.valid; + /* We may safely leave validated status only if both merged keys are valid && validated. + * Otherwise we'll need to revalidate. For instance, one validated but invalid key may add + * revocation signature, or valid key may add certification to the invalid one. */ + validity_.validated = validity_.valid && validity_.validated && src.validated; + /* if expired is true at least in one case then valid and validated are false */ + validity_.expired = false; +} + +bool +pgp_key_t::merge(const pgp_key_t &src) +{ + if (is_subkey() || src.is_subkey()) { + RNP_LOG("wrong key merge call"); + return false; + } + + pgp_transferable_key_t dstkey; + if (transferable_key_from_key(dstkey, *this)) { + RNP_LOG("failed to get transferable key from dstkey"); + return false; + } + + pgp_transferable_key_t srckey; + if (transferable_key_from_key(srckey, src)) { + RNP_LOG("failed to get transferable key from srckey"); + return false; + } + + /* if src is secret key then merged key will become secret as well. */ + if (is_secret_key_pkt(srckey.key.tag) && !is_secret_key_pkt(dstkey.key.tag)) { + pgp_key_pkt_t tmp = dstkey.key; + dstkey.key = srckey.key; + srckey.key = tmp; + /* no subkey processing here - they are separated from the main key */ + } + + if (transferable_key_merge(dstkey, srckey)) { + RNP_LOG("failed to merge transferable keys"); + return false; + } + + pgp_key_t tmpkey; + try { + tmpkey = std::move(dstkey); + for (auto &fp : subkey_fps()) { + tmpkey.add_subkey_fp(fp); + } + for (auto &fp : src.subkey_fps()) { + tmpkey.add_subkey_fp(fp); + } + } catch (const std::exception &e) { + RNP_LOG("failed to process key/add subkey fps: %s", e.what()); + return false; + } + /* check whether key was unlocked and assign secret key data */ + if (is_secret() && !is_locked()) { + /* we may do thing below only because key material is opaque structure without + * pointers! */ + tmpkey.pkt().material = pkt().material; + } else if (src.is_secret() && !src.is_locked()) { + tmpkey.pkt().material = src.pkt().material; + } + /* copy validity status */ + tmpkey.validity_ = validity_; + tmpkey.merge_validity(src.validity_); + + *this = std::move(tmpkey); + return true; +} + +bool +pgp_key_t::merge(const pgp_key_t &src, pgp_key_t *primary) +{ + if (!is_subkey() || !src.is_subkey()) { + RNP_LOG("wrong subkey merge call"); + return false; + } + + pgp_transferable_subkey_t dstkey; + if (transferable_subkey_from_key(dstkey, *this)) { + RNP_LOG("failed to get transferable key from dstkey"); + return false; + } + + pgp_transferable_subkey_t srckey; + if (transferable_subkey_from_key(srckey, src)) { + RNP_LOG("failed to get transferable key from srckey"); + return false; + } + + /* if src is secret key then merged key will become secret as well. */ + if (is_secret_key_pkt(srckey.subkey.tag) && !is_secret_key_pkt(dstkey.subkey.tag)) { + pgp_key_pkt_t tmp = dstkey.subkey; + dstkey.subkey = srckey.subkey; + srckey.subkey = tmp; + } + + if (transferable_subkey_merge(dstkey, srckey)) { + RNP_LOG("failed to merge transferable subkeys"); + return false; + } + + pgp_key_t tmpkey; + try { + tmpkey = pgp_key_t(dstkey, primary); + } catch (const std::exception &e) { + RNP_LOG("failed to process subkey: %s", e.what()); + return false; + } + + /* check whether key was unlocked and assign secret key data */ + if (is_secret() && !is_locked()) { + /* we may do thing below only because key material is opaque structure without + * pointers! */ + tmpkey.pkt().material = pkt().material; + } else if (src.is_secret() && !src.is_locked()) { + tmpkey.pkt().material = src.pkt().material; + } + /* copy validity status */ + tmpkey.validity_ = validity_; + tmpkey.merge_validity(src.validity_); + + *this = std::move(tmpkey); + return true; +} + +size_t +pgp_key_material_t::bits() const +{ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return 8 * mpi_bytes(&rsa.n); + case PGP_PKA_DSA: + return 8 * mpi_bytes(&dsa.p); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return 8 * mpi_bytes(&eg.y); + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + // bn_num_bytes returns value <= curve order + const ec_curve_desc_t *curve = get_curve_desc(ec.curve); + return curve ? curve->bitlen : 0; + } + default: + RNP_LOG("Unknown public key alg: %d", (int) alg); + return 0; + } +} + +size_t +pgp_key_material_t::qbits() const +{ + if (alg != PGP_PKA_DSA) { + return 0; + } + return 8 * mpi_bytes(&dsa.q); +} + +void +pgp_key_material_t::validate(rnp::SecurityContext &ctx, bool reset) +{ + if (!reset && validity.validated) { + return; + } + validity.reset(); + validity.valid = !validate_pgp_key_material(this, &ctx.rng); + validity.validated = true; +} + +bool +pgp_key_material_t::valid() const +{ + return validity.validated && validity.valid; +} diff --git a/src/lib/pgp-key.h b/src/lib/pgp-key.h new file mode 100644 index 0000000..aa088bb --- /dev/null +++ b/src/lib/pgp-key.h @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2017-2021 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RNP_PACKET_KEY_H +#define RNP_PACKET_KEY_H + +#include <stdbool.h> +#include <stdio.h> +#include <vector> +#include <unordered_map> +#include "pass-provider.h" +#include "../librepgp/stream-key.h" +#include <rekey/rnp_key_store.h> +#include "../librepgp/stream-packet.h" +#include "crypto/symmetric.h" +#include "types.h" +#include "sec_profile.hpp" + +/** pgp_rawpacket_t */ +typedef struct pgp_rawpacket_t { + pgp_pkt_type_t tag; + std::vector<uint8_t> raw; + + pgp_rawpacket_t() = default; + pgp_rawpacket_t(const uint8_t *data, size_t len, pgp_pkt_type_t tag) + : tag(tag), + raw(data ? std::vector<uint8_t>(data, data + len) : std::vector<uint8_t>()){}; + pgp_rawpacket_t(const pgp_signature_t &sig); + pgp_rawpacket_t(pgp_key_pkt_t &key); + pgp_rawpacket_t(const pgp_userid_pkt_t &uid); + + void write(pgp_dest_t &dst) const; +} pgp_rawpacket_t; + +/** information about the signature */ +typedef struct pgp_subsig_t { + uint32_t uid{}; /* index in userid array in key for certification sig */ + pgp_signature_t sig{}; /* signature packet */ + pgp_sig_id_t sigid{}; /* signature identifier */ + pgp_rawpacket_t rawpkt{}; /* signature's rawpacket */ + uint8_t trustlevel{}; /* level of trust */ + uint8_t trustamount{}; /* amount of trust */ + uint8_t key_flags{}; /* key flags for certification/direct key sig */ + pgp_user_prefs_t prefs{}; /* user preferences for certification sig */ + pgp_validity_t validity{}; /* signature validity information */ + + pgp_subsig_t() = delete; + pgp_subsig_t(const pgp_signature_t &sig); + + bool validated() const; + bool valid() const; + /** @brief Returns true if signature is certification */ + bool is_cert() const; + /** @brief Returns true if signature is expired */ + bool expired(uint64_t at) const; +} pgp_subsig_t; + +typedef std::unordered_map<pgp_sig_id_t, pgp_subsig_t> pgp_sig_map_t; + +/* userid, built on top of userid packet structure */ +typedef struct pgp_userid_t { + private: + std::vector<pgp_sig_id_t> sigs_{}; /* all signatures related to this userid */ + public: + pgp_userid_pkt_t pkt{}; /* User ID or User Attribute packet as it was loaded */ + pgp_rawpacket_t rawpkt{}; /* Raw packet contents */ + std::string str{}; /* Human-readable representation of the userid */ + bool valid{}; /* User ID is valid, i.e. has valid, non-expired self-signature */ + bool revoked{}; + pgp_revoke_t revocation{}; + + pgp_userid_t(const pgp_userid_pkt_t &pkt); + + size_t sig_count() const; + const pgp_sig_id_t &get_sig(size_t idx) const; + bool has_sig(const pgp_sig_id_t &id) const; + void add_sig(const pgp_sig_id_t &sig); + void replace_sig(const pgp_sig_id_t &id, const pgp_sig_id_t &newsig); + bool del_sig(const pgp_sig_id_t &id); + void clear_sigs(); +} pgp_userid_t; + +#define PGP_UID_NONE ((uint32_t) -1) + +typedef struct rnp_key_store_t rnp_key_store_t; + +/* describes a user's key */ +struct pgp_key_t { + private: + pgp_sig_map_t sigs_map_{}; /* map with subsigs stored by their id */ + std::vector<pgp_sig_id_t> sigs_{}; /* subsig ids to lookup actual sig in map */ + std::vector<pgp_sig_id_t> keysigs_{}; /* direct-key signature ids in the original order */ + std::vector<pgp_userid_t> uids_{}; /* array of user ids */ + pgp_key_pkt_t pkt_{}; /* pubkey/seckey data packet */ + uint8_t flags_{}; /* key flags */ + uint32_t expiration_{}; /* key expiration time, if available */ + pgp_key_id_t keyid_{}; + pgp_fingerprint_t fingerprint_{}; + pgp_key_grip_t grip_{}; + pgp_fingerprint_t primary_fp_{}; /* fingerprint of the primary key (for subkeys) */ + bool primary_fp_set_{}; + std::vector<pgp_fingerprint_t> + subkey_fps_{}; /* array of subkey fingerprints (for primary keys) */ + pgp_rawpacket_t rawpkt_{}; /* key raw packet */ + uint32_t uid0_{}; /* primary uid index in uids array */ + bool uid0_set_{}; /* flag for the above */ + bool revoked_{}; /* key has been revoked */ + pgp_revoke_t revocation_{}; /* revocation reason */ + pgp_validity_t validity_{}; /* key's validity */ + uint64_t valid_till_{}; /* date till which key is/was valid */ + + pgp_subsig_t *latest_uid_selfcert(uint32_t uid); + void validate_primary(rnp_key_store_t &keyring); + void merge_validity(const pgp_validity_t &src); + uint64_t valid_till_common(bool expiry) const; + bool write_sec_pgp(pgp_dest_t & dst, + pgp_key_pkt_t & seckey, + const std::string &password, + rnp::RNG & rng); + + public: + pgp_key_store_format_t format{}; /* the format of the key in packets[0] */ + + pgp_key_t() = default; + pgp_key_t(const pgp_key_pkt_t &pkt); + pgp_key_t(const pgp_key_pkt_t &pkt, pgp_key_t &primary); + pgp_key_t(const pgp_key_t &src, bool pubonly = false); + pgp_key_t(const pgp_transferable_key_t &src); + pgp_key_t(const pgp_transferable_subkey_t &src, pgp_key_t *primary); + pgp_key_t &operator=(const pgp_key_t &) = default; + pgp_key_t &operator=(pgp_key_t &&) = default; + + size_t sig_count() const; + pgp_subsig_t & get_sig(size_t idx); + const pgp_subsig_t &get_sig(size_t idx) const; + bool has_sig(const pgp_sig_id_t &id) const; + pgp_subsig_t & replace_sig(const pgp_sig_id_t &id, const pgp_signature_t &newsig); + pgp_subsig_t & get_sig(const pgp_sig_id_t &id); + const pgp_subsig_t &get_sig(const pgp_sig_id_t &id) const; + pgp_subsig_t & add_sig(const pgp_signature_t &sig, size_t uid = PGP_UID_NONE); + bool del_sig(const pgp_sig_id_t &sigid); + size_t del_sigs(const std::vector<pgp_sig_id_t> &sigs); + size_t keysig_count() const; + pgp_subsig_t & get_keysig(size_t idx); + size_t uid_count() const; + pgp_userid_t & get_uid(size_t idx); + const pgp_userid_t &get_uid(size_t idx) const; + pgp_userid_t & add_uid(const pgp_transferable_userid_t &uid); + bool has_uid(const std::string &uid) const; + void del_uid(size_t idx); + bool has_primary_uid() const; + uint32_t get_primary_uid() const; + bool revoked() const; + const pgp_revoke_t &revocation() const; + void clear_revokes(); + + const pgp_key_pkt_t &pkt() const; + pgp_key_pkt_t & pkt(); + void set_pkt(const pgp_key_pkt_t &pkt); + + pgp_key_material_t &material(); + + pgp_pubkey_alg_t alg() const; + pgp_curve_t curve() const; + pgp_version_t version() const; + pgp_pkt_type_t type() const; + bool encrypted() const; + uint8_t flags() const; + bool can_sign() const; + bool can_certify() const; + bool can_encrypt() const; + bool has_secret() const; + /** + * @brief Check whether key is usable for the specified operation. + * + * @param op operation to check. + * @param if_secret check whether secret part of this key could be usable for op. + * @return true if key (or corresponding secret key) is usable or false otherwise. + */ + bool usable_for(pgp_op_t op, bool if_secret = false) const; + /** @brief Get key's expiration time in seconds. If 0 then it doesn't expire. */ + uint32_t expiration() const; + /** @brief Check whether key is expired. Must be validated before that. */ + bool expired() const; + /** @brief Get key's creation time in seconds since Jan, 1 1970. */ + uint32_t creation() const; + bool is_public() const; + bool is_secret() const; + bool is_primary() const; + bool is_subkey() const; + /** @brief check if a key is currently locked, i.e. secret fields are not decrypted. + * Note: Key locking does not apply to unprotected keys. + */ + bool is_locked() const; + /** @brief check if a key is currently protected, i.e. its secret data is encrypted */ + bool is_protected() const; + + bool valid() const; + bool validated() const; + /** @brief return time till which key is considered to be valid */ + uint64_t valid_till() const; + /** @brief check whether key was/will be valid at the specified time */ + bool valid_at(uint64_t timestamp) const; + + /** @brief Get key's id */ + const pgp_key_id_t &keyid() const; + /** @brief Get key's fingerprint */ + const pgp_fingerprint_t &fp() const; + /** @brief Get key's grip */ + const pgp_key_grip_t &grip() const; + /** @brief Get primary key's fingerprint for the subkey, if it is available. + * Note: will throw if it is not available, use has_primary_fp() to check. + */ + const pgp_fingerprint_t &primary_fp() const; + /** @brief Check whether key has primary key's fingerprint */ + bool has_primary_fp() const; + /** @brief Clean primary_fp */ + void unset_primary_fp(); + /** @brief Link key with subkey via primary_fp and subkey_fps list */ + void link_subkey_fp(pgp_key_t &subkey); + /** + * @brief Add subkey fp to key's list. + * Note: this function will check for duplicates. + */ + void add_subkey_fp(const pgp_fingerprint_t &fp); + /** @brief Get the number of pgp key's subkeys. */ + size_t subkey_count() const; + /** @brief Remove subkey fingerprint from key's list. */ + void remove_subkey_fp(const pgp_fingerprint_t &fp); + /** + * @brief Get the pgp key's subkey fingerprint + * @return fingerprint or throws std::out_of_range exception + */ + const pgp_fingerprint_t & get_subkey_fp(size_t idx) const; + const std::vector<pgp_fingerprint_t> &subkey_fps() const; + + size_t rawpkt_count() const; + pgp_rawpacket_t & rawpkt(); + const pgp_rawpacket_t &rawpkt() const; + void set_rawpkt(const pgp_rawpacket_t &src); + /** @brief write secret key data to the rawpkt, optionally encrypting with password */ + bool write_sec_rawpkt(pgp_key_pkt_t & seckey, + const std::string & password, + rnp::SecurityContext &ctx); + + /** @brief Unlock a key, i.e. decrypt its secret data so it can be used for + * signing/decryption. + * Note: Key locking does not apply to unprotected keys. + * + * @param pass_provider the password provider that may be used to unlock the key + * @param op operation for which secret key should be unloacked + * @return true if the key was unlocked, false otherwise + **/ + bool unlock(const pgp_password_provider_t &provider, pgp_op_t op = PGP_OP_UNLOCK); + /** @brief Lock a key, i.e. cleanup decrypted secret data. + * Note: Key locking does not apply to unprotected keys. + * + * @param key the key + * @return true if the key was locked, false otherwise + **/ + bool lock(); + /** @brief Add protection to an unlocked key, i.e. encrypt its secret data with specified + * parameters. */ + bool protect(const rnp_key_protection_params_t &protection, + const pgp_password_provider_t & password_provider, + rnp::SecurityContext & ctx); + /** @brief Add/change protection of a key */ + bool protect(pgp_key_pkt_t & decrypted, + const rnp_key_protection_params_t &protection, + const std::string & new_password, + rnp::SecurityContext & ctx); + /** @brief Remove protection from a key, i.e. leave secret fields unencrypted */ + bool unprotect(const pgp_password_provider_t &password_provider, + rnp::SecurityContext & ctx); + + /** @brief Write key's packets to the output. */ + void write(pgp_dest_t &dst) const; + /** + * @brief Write OpenPGP key packets (including subkeys) to the specified stream + * + * @param dst stream to write packets + * @param keyring keyring, which will be searched for subkeys. Pass NULL to skip subkeys. + * @return void, but error may be checked via dst.werr + */ + void write_xfer(pgp_dest_t &dst, const rnp_key_store_t *keyring = NULL) const; + /** + * @brief Export key with subkey as it is required by Autocrypt (5-packet sequence: key, + * uid, sig, subkey, sig). + * + * @param dst stream to write packets + * @param sub subkey + * @param uid index of uid to export + * @return true on success or false otherwise + */ + bool write_autocrypt(pgp_dest_t &dst, pgp_key_t &sub, uint32_t uid); + + /** + * @brief Get the latest valid self-signature with information about the primary key for + * the specified uid (including the special cases). It could be userid certification + * or direct-key signature. + * + * @param uid uid for which latest self-signature should be returned, + * PGP_UID_NONE for direct-key signature, + * PGP_UID_PRIMARY for any primary key, + * PGP_UID_ANY for any uid. + * @return pointer to signature object or NULL if failed/not found. + */ + pgp_subsig_t *latest_selfsig(uint32_t uid); + + /** + * @brief Get the latest valid subkey binding. Should be called on subkey. + * + * @param validated set to true whether binding signature must be validated + * @return pointer to signature object or NULL if failed/not found. + */ + pgp_subsig_t *latest_binding(bool validated = true); + + /** @brief Returns true if signature is produced by the key itself. */ + bool is_signer(const pgp_subsig_t &sig) const; + + /** @brief Returns true if key is expired according to sig. */ + bool expired_with(const pgp_subsig_t &sig, uint64_t at) const; + + /** @brief Check whether signature is key's self certification. */ + bool is_self_cert(const pgp_subsig_t &sig) const; + + /** @brief Check whether signature is key's direct-key self-signature */ + bool is_direct_self(const pgp_subsig_t &sig) const; + + /** @brief Check whether signature is key's/subkey's revocation */ + bool is_revocation(const pgp_subsig_t &sig) const; + + /** @brief Check whether signature is userid revocation */ + bool is_uid_revocation(const pgp_subsig_t &sig) const; + + /** @brief Check whether signature is subkey binding */ + bool is_binding(const pgp_subsig_t &sig) const; + + /** + * @brief Validate key's signature, assuming that 'this' is a signing key. + * + * @param key key or subkey to which signature belongs. + * @param sig signature to validate. + * @param ctx Populated security context. + */ + void validate_sig(const pgp_key_t & key, + pgp_subsig_t & sig, + const rnp::SecurityContext &ctx) const noexcept; + + /** + * @brief Validate signature, assuming that 'this' is a signing key. + * + * @param sinfo populated signature info. Validation results will be stored here. + * @param hash hash, feed with all signed data except signature trailer. + * @param ctx Populated security context. + */ + void validate_sig(pgp_signature_info_t & sinfo, + rnp::Hash & hash, + const rnp::SecurityContext &ctx) const noexcept; + + /** + * @brief Validate certification. + * + * @param sinfo populated signature info. Validation results will be stored here. + * @param key key packet to which certification belongs. + * @param uid userid which is bound by certification to the key packet. + */ + void validate_cert(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & key, + const pgp_userid_pkt_t & uid, + const rnp::SecurityContext &ctx) const; + + /** + * @brief Validate subkey binding. + * + * @param sinfo populated signature info. Validation results will be stored here. + * @param subkey subkey packet. + */ + void validate_binding(pgp_signature_info_t & sinfo, + const pgp_key_t & subkey, + const rnp::SecurityContext &ctx) const; + + /** + * @brief Validate subkey revocation. + * + * @param sinfo populated signature info. Validation results will be stored here. + * @param subkey subkey packet. + */ + void validate_sub_rev(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & subkey, + const rnp::SecurityContext &ctx) const; + + /** + * @brief Validate direct-key signature. + * + * @param sinfo populated signature info. Validation results will be stored here. + */ + void validate_direct(pgp_signature_info_t &sinfo, const rnp::SecurityContext &ctx) const; + + void validate_self_signatures(const rnp::SecurityContext &ctx); + void validate_self_signatures(pgp_key_t &primary, const rnp::SecurityContext &ctx); + void validate(rnp_key_store_t &keyring); + void validate_subkey(pgp_key_t *primary, const rnp::SecurityContext &ctx); + void revalidate(rnp_key_store_t &keyring); + void mark_valid(); + /** + * @brief Fill common signature parameters, assuming that current key is a signing one. + * @param sig signature to init. + * @param hash hash algorithm to use (may be changed if it is not suitable for public key + * algorithm). + * @param creation signature's creation time. + */ + void sign_init(pgp_signature_t &sig, pgp_hash_alg_t hash, uint64_t creation) const; + /** + * @brief Calculate a certification and fill signature material. + * Note: secret key must be unlocked before calling this function. + * + * @param key key packet to sign. May be both public and secret. Could be signing key's + * packet for self-signature, or any other one for cross-key certification. + * @param uid uid to certify. + * @param sig signature, pre-populated with all of the required data, except the + * signature material. + */ + void sign_cert(const pgp_key_pkt_t & key, + const pgp_userid_pkt_t &uid, + pgp_signature_t & sig, + rnp::SecurityContext & ctx); + + /** + * @brief Calculate direct-key signature. + * Note: secret key must be unlocked before calling this function. + * + * @param key key packet to sign. May be both public and secret. + * @param sig signature, pre-populated with all of the required data, except the + * signature material. + */ + void sign_direct(const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx); + + /** + * @brief Calculate subkey or primary key binding. + * Note: this will not embed primary key binding for the signing subkey, it should + * be added by the caller. + * + * @param key subkey or primary key packet, may be both public or secret. + * @param sig signature, pre-populated with all of the required data, except the + * signature material. + */ + void sign_binding(const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx); + + /** + * @brief Calculate subkey binding. + * Note: secret key must be unlocked before calling this function. If subsign is + * true then subkey must be secret and unlocked as well so function can calculate + * primary key binding. + * + * @param sub subkey to bind to the primary key. If subsign is true then must be unlocked + * secret key. + * @param sig signature, pre-populated with all of the required data, except the + * signature material. + */ + void sign_subkey_binding(pgp_key_t & sub, + pgp_signature_t & sig, + rnp::SecurityContext &ctx, + bool subsign = false); + + /** + * @brief Generate key or subkey revocation signature. + * + * @param revoke revocation information. + * @param key key or subkey packet to revoke. + * @param sig object to store revocation signature. Will be populated in method call. + */ + void gen_revocation(const pgp_revoke_t & revoke, + pgp_hash_alg_t hash, + const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx); + + /** + * @brief Add and certify userid. + * Note: secret key must be unlocked before calling this function. + * + * @param cert certification and userid parameters. + * @param hash hash algorithm to use during signing. See sign_init() for more details. + * @param ctx security context. + * @param pubkey if non-NULL then userid and certification will be added to this key as + * well. + */ + void add_uid_cert(rnp_selfsig_cert_info_t &cert, + pgp_hash_alg_t hash, + rnp::SecurityContext & ctx, + pgp_key_t * pubkey = nullptr); + + /** + * @brief Calculate and add subkey binding signature. + * Note: must be called on the unlocked secret primary key. Calculated signature is + * added to the subkey. + * + * @param subsec secret subkey. + * @param subpub subkey's public part (so signature is added to both). + * @param binding information about subkey to put to the signature. + * @param hash hash algorithm to use (may be adjusted according to key and subkey + * algorithms) + */ + void add_sub_binding(pgp_key_t & subsec, + pgp_key_t & subpub, + const rnp_selfsig_binding_info_t &binding, + pgp_hash_alg_t hash, + rnp::SecurityContext & ctx); + + /** @brief Refresh internal fields after primary key is updated */ + bool refresh_data(const rnp::SecurityContext &ctx); + /** @brief Refresh internal fields after subkey is updated */ + bool refresh_data(pgp_key_t *primary, const rnp::SecurityContext &ctx); + /** @brief Merge primary key with the src, i.e. add all new userids/signatures/subkeys */ + bool merge(const pgp_key_t &src); + /** @brief Merge subkey with the source, i.e. add all new signatures */ + bool merge(const pgp_key_t &src, pgp_key_t *primary); +}; + +namespace rnp { +class KeyLocker { + bool lock_; + pgp_key_t &key_; + + public: + KeyLocker(pgp_key_t &key) : lock_(key.is_locked()), key_(key) + { + } + + ~KeyLocker() + { + if (lock_ && !key_.is_locked()) { + key_.lock(); + } + } +}; +}; // namespace rnp + +pgp_key_pkt_t *pgp_decrypt_seckey_pgp(const pgp_rawpacket_t &raw, + const pgp_key_pkt_t & key, + const char * password); + +pgp_key_pkt_t *pgp_decrypt_seckey(const pgp_key_t &, + const pgp_password_provider_t &, + const pgp_password_ctx_t &); + +/** + * @brief Get the signer's key for signature + * + * @param sig signature + * @param keyring keyring to search for the key. May be NULL. + * @param prov key provider to request needed key, may be NULL. + * @return pointer to the key or NULL if key is not found. + */ +pgp_key_t *pgp_sig_get_signer(const pgp_subsig_t &sig, + rnp_key_store_t * keyring, + pgp_key_provider_t *prov); + +/** + * @brief Get the key's subkey by its index + * + * @param key primary key + * @param store key store which will be searched for subkeys + * @param idx index of the subkey + * @return pointer to the subkey or NULL if subkey not found + */ +pgp_key_t *pgp_key_get_subkey(const pgp_key_t *key, rnp_key_store_t *store, size_t idx); + +pgp_key_flags_t pgp_pk_alg_capabilities(pgp_pubkey_alg_t alg); + +bool pgp_key_set_expiration(pgp_key_t * key, + pgp_key_t * signer, + uint32_t expiry, + const pgp_password_provider_t &prov, + rnp::SecurityContext & ctx); + +bool pgp_subkey_set_expiration(pgp_key_t * sub, + pgp_key_t * primsec, + pgp_key_t * secsub, + uint32_t expiry, + const pgp_password_provider_t &prov, + rnp::SecurityContext & ctx); + +/** Find a key or it's subkey, suitable for a particular operation + * + * If the key passed is suitable, it will be returned. + * Otherwise, its subkeys (if it is a primary w/subs) + * will be checked. NULL will be returned if no suitable + * key is found. + * + * @param op the operation for which the key should be suitable + * @param key the key + * @param key_provider the key provider. This will be used + * if/when subkeys are checked. + * @param no_primary set true if only subkeys must be returned + * + * @returns key or last created subkey with desired usage flag + * set or NULL if not found + */ +pgp_key_t *find_suitable_key(pgp_op_t op, + pgp_key_t * key, + pgp_key_provider_t *key_provider, + bool no_primary = false); + +/* + * Picks up hash algorithm according to domain parameters set + * in `pubkey' and user provided hash. That's mostly because DSA + * and ECDSA needs special treatment. + * + * @param hash set by the caller + * @param pubkey initialized public key + * + * @returns hash algorithm that must be use for operation (mostly + signing with secure key which corresponds to 'pubkey') + */ +pgp_hash_alg_t pgp_hash_adjust_alg_to_key(pgp_hash_alg_t hash, const pgp_key_pkt_t *pubkey); + +#endif // RNP_PACKET_KEY_H diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp new file mode 100644 index 0000000..24c46f9 --- /dev/null +++ b/src/lib/rnp.cpp @@ -0,0 +1,8403 @@ +/*- + * Copyright (c) 2017-2021, Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "crypto.h" +#include "crypto/common.h" +#include "pgp-key.h" +#include "defaults.h" +#include <assert.h> +#include <json_object.h> +#include <json.h> +#include <librekey/key_store_pgp.h> +#include <librepgp/stream-ctx.h> +#include <librepgp/stream-common.h> +#include <librepgp/stream-armor.h> +#include <librepgp/stream-parse.h> +#include <librepgp/stream-write.h> +#include <librepgp/stream-sig.h> +#include <librepgp/stream-packet.h> +#include <librepgp/stream-key.h> +#include <librepgp/stream-dump.h> +#include <rnp/rnp.h> +#include <stdarg.h> +#include <stdlib.h> +#ifdef _MSC_VER +#include "uniwin.h" +#include <inttypes.h> +#else +#include <unistd.h> +#endif +#include <string.h> +#include <sys/stat.h> +#include <stdexcept> +#include "utils.h" +#include "str-utils.h" +#include "json-utils.h" +#include "version.h" +#include "ffi-priv-types.h" +#include "file-utils.h" + +#define FFI_LOG(ffi, ...) \ + do { \ + FILE *fp = stderr; \ + if (ffi && ffi->errs) { \ + fp = ffi->errs; \ + } \ + RNP_LOG_FD(fp, __VA_ARGS__); \ + } while (0) + +static pgp_key_t *get_key_require_public(rnp_key_handle_t handle); +static pgp_key_t *get_key_prefer_public(rnp_key_handle_t handle); +static pgp_key_t *get_key_require_secret(rnp_key_handle_t handle); + +static bool locator_to_str(const pgp_key_search_t &locator, + const char ** identifier_type, + char * identifier, + size_t identifier_size); + +static bool rnp_password_cb_bounce(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata_void); + +static rnp_result_t rnp_dump_src_to_json(pgp_source_t *src, uint32_t flags, char **result); + +static bool +call_key_callback(rnp_ffi_t ffi, const pgp_key_search_t &search, bool secret) +{ + if (!ffi->getkeycb) { + return false; + } + char identifier[RNP_LOCATOR_MAX_SIZE]; + const char *identifier_type = NULL; + if (!locator_to_str(search, &identifier_type, identifier, sizeof(identifier))) { + return false; + } + + ffi->getkeycb(ffi, ffi->getkeycb_ctx, identifier_type, identifier, secret); + return true; +} + +static pgp_key_t * +find_key(rnp_ffi_t ffi, + const pgp_key_search_t &search, + bool secret, + bool try_key_provider, + pgp_key_t * after = NULL) +{ + pgp_key_t *key = + rnp_key_store_search(secret ? ffi->secring : ffi->pubring, &search, after); + if (!key && try_key_provider && call_key_callback(ffi, search, secret)) { + // recurse and try the store search above once more + return find_key(ffi, search, secret, false, after); + } + return key; +} + +static pgp_key_t * +ffi_key_provider(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + rnp_ffi_t ffi = (rnp_ffi_t) userdata; + return find_key(ffi, ctx->search, ctx->secret, true); +} + +static void +rnp_ctx_init_ffi(rnp_ctx_t &ctx, rnp_ffi_t ffi) +{ + ctx.ctx = &ffi->context; + ctx.ealg = DEFAULT_PGP_SYMM_ALG; + ctx.aalg = PGP_AEAD_NONE; + ctx.abits = DEFAULT_AEAD_CHUNK_BITS; +} + +static const id_str_pair sig_type_map[] = {{PGP_SIG_BINARY, "binary"}, + {PGP_SIG_TEXT, "text"}, + {PGP_SIG_STANDALONE, "standalone"}, + {PGP_CERT_GENERIC, "certification (generic)"}, + {PGP_CERT_PERSONA, "certification (persona)"}, + {PGP_CERT_CASUAL, "certification (casual)"}, + {PGP_CERT_POSITIVE, "certification (positive)"}, + {PGP_SIG_SUBKEY, "subkey binding"}, + {PGP_SIG_PRIMARY, "primary key binding"}, + {PGP_SIG_DIRECT, "direct"}, + {PGP_SIG_REV_KEY, "key revocation"}, + {PGP_SIG_REV_SUBKEY, "subkey revocation"}, + {PGP_SIG_REV_CERT, "certification revocation"}, + {PGP_SIG_TIMESTAMP, "timestamp"}, + {PGP_SIG_3RD_PARTY, "third-party"}, + {0, NULL}}; + +static const id_str_pair pubkey_alg_map[] = { + {PGP_PKA_RSA, RNP_ALGNAME_RSA}, + {PGP_PKA_RSA_ENCRYPT_ONLY, RNP_ALGNAME_RSA}, + {PGP_PKA_RSA_SIGN_ONLY, RNP_ALGNAME_RSA}, + {PGP_PKA_ELGAMAL, RNP_ALGNAME_ELGAMAL}, + {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, RNP_ALGNAME_ELGAMAL}, + {PGP_PKA_DSA, RNP_ALGNAME_DSA}, + {PGP_PKA_ECDH, RNP_ALGNAME_ECDH}, + {PGP_PKA_ECDSA, RNP_ALGNAME_ECDSA}, + {PGP_PKA_EDDSA, RNP_ALGNAME_EDDSA}, + {PGP_PKA_SM2, RNP_ALGNAME_SM2}, + {0, NULL}}; + +static const id_str_pair symm_alg_map[] = {{PGP_SA_IDEA, RNP_ALGNAME_IDEA}, + {PGP_SA_TRIPLEDES, RNP_ALGNAME_TRIPLEDES}, + {PGP_SA_CAST5, RNP_ALGNAME_CAST5}, + {PGP_SA_BLOWFISH, RNP_ALGNAME_BLOWFISH}, + {PGP_SA_AES_128, RNP_ALGNAME_AES_128}, + {PGP_SA_AES_192, RNP_ALGNAME_AES_192}, + {PGP_SA_AES_256, RNP_ALGNAME_AES_256}, + {PGP_SA_TWOFISH, RNP_ALGNAME_TWOFISH}, + {PGP_SA_CAMELLIA_128, RNP_ALGNAME_CAMELLIA_128}, + {PGP_SA_CAMELLIA_192, RNP_ALGNAME_CAMELLIA_192}, + {PGP_SA_CAMELLIA_256, RNP_ALGNAME_CAMELLIA_256}, + {PGP_SA_SM4, RNP_ALGNAME_SM4}, + {0, NULL}}; + +static const id_str_pair aead_alg_map[] = { + {PGP_AEAD_NONE, "None"}, {PGP_AEAD_EAX, "EAX"}, {PGP_AEAD_OCB, "OCB"}, {0, NULL}}; + +static const id_str_pair cipher_mode_map[] = {{PGP_CIPHER_MODE_CFB, "CFB"}, + {PGP_CIPHER_MODE_CBC, "CBC"}, + {PGP_CIPHER_MODE_OCB, "OCB"}, + {0, NULL}}; + +static const id_str_pair compress_alg_map[] = {{PGP_C_NONE, "Uncompressed"}, + {PGP_C_ZIP, "ZIP"}, + {PGP_C_ZLIB, "ZLIB"}, + {PGP_C_BZIP2, "BZip2"}, + {0, NULL}}; + +static const id_str_pair hash_alg_map[] = {{PGP_HASH_MD5, RNP_ALGNAME_MD5}, + {PGP_HASH_SHA1, RNP_ALGNAME_SHA1}, + {PGP_HASH_RIPEMD, RNP_ALGNAME_RIPEMD160}, + {PGP_HASH_SHA256, RNP_ALGNAME_SHA256}, + {PGP_HASH_SHA384, RNP_ALGNAME_SHA384}, + {PGP_HASH_SHA512, RNP_ALGNAME_SHA512}, + {PGP_HASH_SHA224, RNP_ALGNAME_SHA224}, + {PGP_HASH_SHA3_256, RNP_ALGNAME_SHA3_256}, + {PGP_HASH_SHA3_512, RNP_ALGNAME_SHA3_512}, + {PGP_HASH_SM3, RNP_ALGNAME_SM3}, + {0, NULL}}; + +static const id_str_pair s2k_type_map[] = { + {PGP_S2KS_SIMPLE, "Simple"}, + {PGP_S2KS_SALTED, "Salted"}, + {PGP_S2KS_ITERATED_AND_SALTED, "Iterated and salted"}, + {0, NULL}}; + +static const id_str_pair key_usage_map[] = { + {PGP_KF_SIGN, "sign"}, + {PGP_KF_CERTIFY, "certify"}, + {PGP_KF_ENCRYPT, "encrypt"}, + {PGP_KF_AUTH, "authenticate"}, + {0, NULL}, +}; + +static const id_str_pair key_flags_map[] = { + {PGP_KF_SPLIT, "split"}, + {PGP_KF_SHARED, "shared"}, + {0, NULL}, +}; + +static const id_str_pair identifier_type_map[] = {{PGP_KEY_SEARCH_USERID, "userid"}, + {PGP_KEY_SEARCH_KEYID, "keyid"}, + {PGP_KEY_SEARCH_FINGERPRINT, "fingerprint"}, + {PGP_KEY_SEARCH_GRIP, "grip"}, + {0, NULL}}; + +static const id_str_pair key_server_prefs_map[] = {{PGP_KEY_SERVER_NO_MODIFY, "no-modify"}, + {0, NULL}}; + +static const id_str_pair armor_type_map[] = {{PGP_ARMORED_MESSAGE, "message"}, + {PGP_ARMORED_PUBLIC_KEY, "public key"}, + {PGP_ARMORED_SECRET_KEY, "secret key"}, + {PGP_ARMORED_SIGNATURE, "signature"}, + {PGP_ARMORED_CLEARTEXT, "cleartext"}, + {0, NULL}}; + +static const id_str_pair key_import_status_map[] = { + {PGP_KEY_IMPORT_STATUS_UNKNOWN, "unknown"}, + {PGP_KEY_IMPORT_STATUS_UNCHANGED, "unchanged"}, + {PGP_KEY_IMPORT_STATUS_UPDATED, "updated"}, + {PGP_KEY_IMPORT_STATUS_NEW, "new"}, + {0, NULL}}; + +static const id_str_pair sig_import_status_map[] = { + {PGP_SIG_IMPORT_STATUS_UNKNOWN, "unknown"}, + {PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY, "unknown key"}, + {PGP_SIG_IMPORT_STATUS_UNCHANGED, "unchanged"}, + {PGP_SIG_IMPORT_STATUS_NEW, "new"}, + {0, NULL}}; + +static const id_str_pair revocation_code_map[] = { + {PGP_REVOCATION_NO_REASON, "no"}, + {PGP_REVOCATION_SUPERSEDED, "superseded"}, + {PGP_REVOCATION_COMPROMISED, "compromised"}, + {PGP_REVOCATION_RETIRED, "retired"}, + {PGP_REVOCATION_NO_LONGER_VALID, "no longer valid"}, + {0, NULL}}; + +static bool +symm_alg_supported(int alg) +{ + return pgp_is_sa_supported(alg, true); +} + +static bool +hash_alg_supported(int alg) +{ + switch (alg) { + case PGP_HASH_MD5: + case PGP_HASH_SHA1: +#if defined(ENABLE_RIPEMD160) + case PGP_HASH_RIPEMD: +#endif + case PGP_HASH_SHA256: + case PGP_HASH_SHA384: + case PGP_HASH_SHA512: + case PGP_HASH_SHA224: + case PGP_HASH_SHA3_256: + case PGP_HASH_SHA3_512: +#if defined(ENABLE_SM2) + case PGP_HASH_SM3: +#endif + return true; + default: + return false; + } +} + +static bool +aead_alg_supported(int alg) +{ + switch (alg) { + case PGP_AEAD_NONE: +#if defined(ENABLE_AEAD) +#if !defined(CRYPTO_BACKEND_OPENSSL) + case PGP_AEAD_EAX: +#endif + case PGP_AEAD_OCB: +#endif + return true; + default: + return false; + } +} + +static bool +pub_alg_supported(int alg) +{ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_ELGAMAL: + case PGP_PKA_DSA: + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: +#if defined(ENABLE_SM2) + case PGP_PKA_SM2: +#endif + return true; + default: + return false; + } +} + +static bool +z_alg_supported(int alg) +{ + switch (alg) { + case PGP_C_NONE: + case PGP_C_ZIP: + case PGP_C_ZLIB: + case PGP_C_BZIP2: + return true; + default: + return false; + } +} + +static bool +curve_str_to_type(const char *str, pgp_curve_t *value) +{ + *value = find_curve_by_name(str); + return curve_supported(*value); +} + +static bool +curve_type_to_str(pgp_curve_t type, const char **str) +{ + const ec_curve_desc_t *desc = get_curve_desc(type); + if (!desc) { + return false; + } + *str = desc->pgp_name; + return true; +} + +static bool +str_to_cipher(const char *str, pgp_symm_alg_t *cipher) +{ + auto alg = id_str_pair::lookup(symm_alg_map, str, PGP_SA_UNKNOWN); + if (!symm_alg_supported(alg)) { + return false; + } + *cipher = static_cast<pgp_symm_alg_t>(alg); + return true; +} + +static bool +str_to_hash_alg(const char *str, pgp_hash_alg_t *hash_alg) +{ + auto alg = id_str_pair::lookup(hash_alg_map, str, PGP_HASH_UNKNOWN); + if (!hash_alg_supported(alg)) { + return false; + } + *hash_alg = static_cast<pgp_hash_alg_t>(alg); + return true; +} + +static bool +str_to_aead_alg(const char *str, pgp_aead_alg_t *aead_alg) +{ + auto alg = id_str_pair::lookup(aead_alg_map, str, PGP_AEAD_UNKNOWN); + if (!aead_alg_supported(alg)) { + return false; + } + *aead_alg = static_cast<pgp_aead_alg_t>(alg); + return true; +} + +static bool +str_to_compression_alg(const char *str, pgp_compression_type_t *zalg) +{ + auto alg = id_str_pair::lookup(compress_alg_map, str, PGP_C_UNKNOWN); + if (!z_alg_supported(alg)) { + return false; + } + *zalg = static_cast<pgp_compression_type_t>(alg); + return true; +} + +static bool +str_to_revocation_type(const char *str, pgp_revocation_type_t *code) +{ + pgp_revocation_type_t rev = static_cast<pgp_revocation_type_t>( + id_str_pair::lookup(revocation_code_map, str, PGP_REVOCATION_NO_REASON)); + if ((rev == PGP_REVOCATION_NO_REASON) && !rnp::str_case_eq(str, "no")) { + return false; + } + *code = rev; + return true; +} + +static bool +str_to_cipher_mode(const char *str, pgp_cipher_mode_t *mode) +{ + pgp_cipher_mode_t c_mode = static_cast<pgp_cipher_mode_t>( + id_str_pair::lookup(cipher_mode_map, str, PGP_CIPHER_MODE_NONE)); + if (c_mode == PGP_CIPHER_MODE_NONE) { + return false; + } + + *mode = c_mode; + return true; +} + +static bool +str_to_pubkey_alg(const char *str, pgp_pubkey_alg_t *pub_alg) +{ + auto alg = id_str_pair::lookup(pubkey_alg_map, str, PGP_PKA_NOTHING); + if (!pub_alg_supported(alg)) { + return false; + } + *pub_alg = static_cast<pgp_pubkey_alg_t>(alg); + return true; +} + +static bool +str_to_key_flag(const char *str, uint8_t *flag) +{ + uint8_t _flag = id_str_pair::lookup(key_usage_map, str); + if (!_flag) { + return false; + } + *flag = _flag; + return true; +} + +static bool +parse_ks_format(pgp_key_store_format_t *key_store_format, const char *format) +{ + if (!strcmp(format, RNP_KEYSTORE_GPG)) { + *key_store_format = PGP_KEY_STORE_GPG; + } else if (!strcmp(format, RNP_KEYSTORE_KBX)) { + *key_store_format = PGP_KEY_STORE_KBX; + } else if (!strcmp(format, RNP_KEYSTORE_G10)) { + *key_store_format = PGP_KEY_STORE_G10; + } else { + return false; + } + return true; +} + +static rnp_result_t +hex_encode_value(const uint8_t * value, + size_t len, + char ** res, + rnp::hex_format_t format = rnp::HEX_UPPERCASE) +{ + size_t hex_len = len * 2 + 1; + *res = (char *) malloc(hex_len); + if (!*res) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!rnp::hex_encode(value, len, *res, hex_len, format)) { + free(*res); + *res = NULL; + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} + +static rnp_result_t +get_map_value(const id_str_pair *map, int val, char **res) +{ + const char *str = id_str_pair::lookup(map, val, NULL); + if (!str) { + return RNP_ERROR_BAD_PARAMETERS; + } + char *strcp = strdup(str); + if (!strcp) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *res = strcp; + return RNP_SUCCESS; +} + +static rnp_result_t +ret_str_value(const char *str, char **res) +{ + if (!str) { + return RNP_ERROR_BAD_PARAMETERS; + } + char *strcp = strdup(str); + if (!strcp) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *res = strcp; + return RNP_SUCCESS; +} + +static uint32_t +ffi_exception(FILE *fp, const char *func, const char *msg, uint32_t ret = RNP_ERROR_GENERIC) +{ + if (rnp_log_switch()) { + fprintf( + fp, "[%s()] Error 0x%08X (%s): %s\n", func, ret, rnp_result_to_string(ret), msg); + } + return ret; +} + +#define FFI_GUARD_FP(fp) \ + catch (rnp::rnp_exception & e) \ + { \ + return ffi_exception((fp), __func__, e.what(), e.code()); \ + } \ + catch (std::bad_alloc &) \ + { \ + return ffi_exception((fp), __func__, "bad_alloc", RNP_ERROR_OUT_OF_MEMORY); \ + } \ + catch (std::exception & e) \ + { \ + return ffi_exception((fp), __func__, e.what()); \ + } \ + catch (...) \ + { \ + return ffi_exception((fp), __func__, "unknown exception"); \ + } + +#define FFI_GUARD FFI_GUARD_FP((stderr)) + +rnp_ffi_st::rnp_ffi_st(pgp_key_store_format_t pub_fmt, pgp_key_store_format_t sec_fmt) +{ + errs = stderr; + pubring = new rnp_key_store_t(pub_fmt, "", context); + secring = new rnp_key_store_t(sec_fmt, "", context); + getkeycb = NULL; + getkeycb_ctx = NULL; + getpasscb = NULL; + getpasscb_ctx = NULL; + key_provider.callback = ffi_key_provider; + key_provider.userdata = this; + pass_provider.callback = rnp_password_cb_bounce; + pass_provider.userdata = this; +} + +rnp::RNG & +rnp_ffi_st::rng() noexcept +{ + return context.rng; +} + +rnp::SecurityProfile & +rnp_ffi_st::profile() noexcept +{ + return context.profile; +} + +rnp_result_t +rnp_ffi_create(rnp_ffi_t *ffi, const char *pub_format, const char *sec_format) +try { + // checks + if (!ffi || !pub_format || !sec_format) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_store_format_t pub_ks_format = PGP_KEY_STORE_UNKNOWN; + pgp_key_store_format_t sec_ks_format = PGP_KEY_STORE_UNKNOWN; + if (!parse_ks_format(&pub_ks_format, pub_format) || + !parse_ks_format(&sec_ks_format, sec_format)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + struct rnp_ffi_st *ob = new rnp_ffi_st(pub_ks_format, sec_ks_format); + *ffi = ob; + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +is_std_file(FILE *fp) +{ + return fp == stdout || fp == stderr; +} + +static void +close_io_file(FILE **fp) +{ + if (*fp && !is_std_file(*fp)) { + fclose(*fp); + } + *fp = NULL; +} + +rnp_ffi_st::~rnp_ffi_st() +{ + close_io_file(&errs); + delete pubring; + delete secring; +} + +rnp_result_t +rnp_ffi_destroy(rnp_ffi_t ffi) +try { + if (ffi) { + delete ffi; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_ffi_set_log_fd(rnp_ffi_t ffi, int fd) +try { + // checks + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + + // open + FILE *errs = rnp_fdopen(fd, "a"); + if (!errs) { + return RNP_ERROR_ACCESS; + } + // close previous streams and replace them + close_io_file(&ffi->errs); + ffi->errs = errs; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_ffi_set_key_provider(rnp_ffi_t ffi, rnp_get_key_cb getkeycb, void *getkeycb_ctx) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + ffi->getkeycb = getkeycb; + ffi->getkeycb_ctx = getkeycb_ctx; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_ffi_set_pass_provider(rnp_ffi_t ffi, rnp_password_cb getpasscb, void *getpasscb_ctx) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + ffi->getpasscb = getpasscb; + ffi->getpasscb_ctx = getpasscb_ctx; + return RNP_SUCCESS; +} +FFI_GUARD + +static const char * +operation_description(uint8_t op) +{ + switch (op) { + case PGP_OP_ADD_SUBKEY: + return "add subkey"; + case PGP_OP_ADD_USERID: + return "add userid"; + case PGP_OP_SIGN: + return "sign"; + case PGP_OP_DECRYPT: + return "decrypt"; + case PGP_OP_UNLOCK: + return "unlock"; + case PGP_OP_PROTECT: + return "protect"; + case PGP_OP_UNPROTECT: + return "unprotect"; + case PGP_OP_DECRYPT_SYM: + return "decrypt (symmetric)"; + case PGP_OP_ENCRYPT_SYM: + return "encrypt (symmetric)"; + default: + return "unknown"; + } +} + +static bool +rnp_password_cb_bounce(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata_void) +{ + rnp_ffi_t ffi = (rnp_ffi_t) userdata_void; + + if (!ffi || !ffi->getpasscb) { + return false; + } + + struct rnp_key_handle_st key = {}; + key.ffi = ffi; + key.sec = (pgp_key_t *) ctx->key; + return ffi->getpasscb(ffi, + ffi->getpasscb_ctx, + ctx->key ? &key : NULL, + operation_description(ctx->op), + password, + password_size); +} + +const char * +rnp_result_to_string(rnp_result_t result) +{ + switch (result) { + case RNP_SUCCESS: + return "Success"; + + case RNP_ERROR_GENERIC: + return "Unknown error"; + case RNP_ERROR_BAD_FORMAT: + return "Bad format"; + case RNP_ERROR_BAD_PARAMETERS: + return "Bad parameters"; + case RNP_ERROR_NOT_IMPLEMENTED: + return "Not implemented"; + case RNP_ERROR_NOT_SUPPORTED: + return "Not supported"; + case RNP_ERROR_OUT_OF_MEMORY: + return "Out of memory"; + case RNP_ERROR_SHORT_BUFFER: + return "Buffer too short"; + case RNP_ERROR_NULL_POINTER: + return "Null pointer"; + + case RNP_ERROR_ACCESS: + return "Error accessing file"; + case RNP_ERROR_READ: + return "Error reading file"; + case RNP_ERROR_WRITE: + return "Error writing file"; + + case RNP_ERROR_BAD_STATE: + return "Bad state"; + case RNP_ERROR_MAC_INVALID: + return "Invalid MAC"; + case RNP_ERROR_SIGNATURE_INVALID: + return "Invalid signature"; + case RNP_ERROR_KEY_GENERATION: + return "Error during key generation"; + case RNP_ERROR_BAD_PASSWORD: + return "Bad password"; + case RNP_ERROR_KEY_NOT_FOUND: + return "Key not found"; + case RNP_ERROR_NO_SUITABLE_KEY: + return "No suitable key"; + case RNP_ERROR_DECRYPT_FAILED: + return "Decryption failed"; + case RNP_ERROR_RNG: + return "Failure of random number generator"; + case RNP_ERROR_SIGNING_FAILED: + return "Signing failed"; + case RNP_ERROR_NO_SIGNATURES_FOUND: + return "No signatures found cannot verify"; + + case RNP_ERROR_SIGNATURE_EXPIRED: + return "Expired signature"; + case RNP_ERROR_VERIFICATION_FAILED: + return "Signature verification failed cannot verify"; + case RNP_ERROR_SIGNATURE_UNKNOWN: + return "Unknown signature"; + + case RNP_ERROR_NOT_ENOUGH_DATA: + return "Not enough data"; + case RNP_ERROR_UNKNOWN_TAG: + return "Unknown tag"; + case RNP_ERROR_PACKET_NOT_CONSUMED: + return "Packet not consumed"; + case RNP_ERROR_NO_USERID: + return "No userid"; + case RNP_ERROR_EOF: + return "EOF detected"; + } + + return "Unsupported error code"; +} + +const char * +rnp_version_string() +{ + return RNP_VERSION_STRING; +} + +const char * +rnp_version_string_full() +{ + return RNP_VERSION_STRING_FULL; +} + +uint32_t +rnp_version() +{ + return RNP_VERSION_CODE; +} + +uint32_t +rnp_version_for(uint32_t major, uint32_t minor, uint32_t patch) +{ + if (major > RNP_VERSION_COMPONENT_MASK || minor > RNP_VERSION_COMPONENT_MASK || + patch > RNP_VERSION_COMPONENT_MASK) { + RNP_LOG("invalid version, out of range: %d.%d.%d", major, minor, patch); + return 0; + } + return RNP_VERSION_CODE_FOR(major, minor, patch); +} + +uint32_t +rnp_version_major(uint32_t version) +{ + return (version >> RNP_VERSION_MAJOR_SHIFT) & RNP_VERSION_COMPONENT_MASK; +} + +uint32_t +rnp_version_minor(uint32_t version) +{ + return (version >> RNP_VERSION_MINOR_SHIFT) & RNP_VERSION_COMPONENT_MASK; +} + +uint32_t +rnp_version_patch(uint32_t version) +{ + return (version >> RNP_VERSION_PATCH_SHIFT) & RNP_VERSION_COMPONENT_MASK; +} + +uint64_t +rnp_version_commit_timestamp() +{ + return RNP_VERSION_COMMIT_TIMESTAMP; +} + +#ifndef RNP_NO_DEPRECATED +rnp_result_t +rnp_enable_debug(const char *file) +try { + return RNP_SUCCESS; +} +FFI_GUARD +#endif + +#ifndef RNP_NO_DEPRECATED +rnp_result_t +rnp_disable_debug() +try { + return RNP_SUCCESS; +} +FFI_GUARD +#endif + +rnp_result_t +rnp_get_default_homedir(char **homedir) +try { + // checks + if (!homedir) { + return RNP_ERROR_NULL_POINTER; + } + + // get the users home dir + auto home = rnp::path::HOME(".rnp"); + if (home.empty()) { + return RNP_ERROR_NOT_SUPPORTED; + } + *homedir = strdup(home.c_str()); + if (!*homedir) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_detect_homedir_info( + const char *homedir, char **pub_format, char **pub_path, char **sec_format, char **sec_path) +try { + // checks + if (!homedir || !pub_format || !pub_path || !sec_format || !sec_path) { + return RNP_ERROR_NULL_POINTER; + } + + // we only support the common cases of GPG+GPG or GPG+G10, we don't + // support unused combinations like KBX+KBX + + *pub_format = NULL; + *pub_path = NULL; + *sec_format = NULL; + *sec_path = NULL; + + // check for pubring.kbx file and for private-keys-v1.d dir + std::string pub = rnp::path::append(homedir, "pubring.kbx"); + std::string sec = rnp::path::append(homedir, "private-keys-v1.d"); + if (rnp::path::exists(pub) && rnp::path::exists(sec, true)) { + *pub_format = strdup("KBX"); + *sec_format = strdup("G10"); + } else { + // check for pubring.gpg and secring.gpg + pub = rnp::path::append(homedir, "pubring.gpg"); + sec = rnp::path::append(homedir, "secring.gpg"); + if (rnp::path::exists(pub) && rnp::path::exists(sec)) { + *pub_format = strdup("GPG"); + *sec_format = strdup("GPG"); + } else { + // we leave the *formats as NULL if we were not able to determine the format + // (but no error occurred) + return RNP_SUCCESS; + } + } + + // set pathes + *pub_path = strdup(pub.c_str()); + *sec_path = strdup(sec.c_str()); + + // check for allocation failures + if (*pub_format && *pub_path && *sec_format && *sec_path) { + return RNP_SUCCESS; + } + + free(*pub_format); + *pub_format = NULL; + free(*pub_path); + *pub_path = NULL; + free(*sec_format); + *sec_format = NULL; + free(*sec_path); + *sec_path = NULL; + return RNP_ERROR_OUT_OF_MEMORY; +} +FFI_GUARD + +rnp_result_t +rnp_detect_key_format(const uint8_t buf[], size_t buf_len, char **format) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + + // checks + if (!buf || !format) { + return RNP_ERROR_NULL_POINTER; + } + if (!buf_len) { + return RNP_ERROR_SHORT_BUFFER; + } + + *format = NULL; + // ordered from most reliable detection to least + const char *guess = NULL; + if (buf_len >= 12 && memcmp(buf + 8, "KBXf", 4) == 0) { + // KBX has a magic KBXf marker + guess = "KBX"; + } else if (buf_len >= 5 && memcmp(buf, "-----", 5) == 0) { + // likely armored GPG + guess = "GPG"; + } else if (buf[0] == '(') { + // G10 is s-exprs and should start end end with parentheses + guess = "G10"; + } else if (buf[0] & PGP_PTAG_ALWAYS_SET) { + // this is harder to reliably determine, but could likely be improved + guess = "GPG"; + } + if (guess) { + *format = strdup(guess); + if (!*format) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + + // success + ret = RNP_SUCCESS; +done: + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_calculate_iterations(const char *hash, size_t msec, size_t *iterations) +try { + if (!hash || !iterations) { + return RNP_ERROR_NULL_POINTER; + } + pgp_hash_alg_t halg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &halg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *iterations = pgp_s2k_compute_iters(halg, msec, 0); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_supports_feature(const char *type, const char *name, bool *supported) +try { + if (!type || !name || !supported) { + return RNP_ERROR_NULL_POINTER; + } + if (rnp::str_case_eq(type, RNP_FEATURE_SYMM_ALG)) { + pgp_symm_alg_t alg = PGP_SA_UNKNOWN; + *supported = str_to_cipher(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_AEAD_ALG)) { + pgp_aead_alg_t alg = PGP_AEAD_UNKNOWN; + *supported = str_to_aead_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PROT_MODE)) { + // for now we support only CFB for key encryption + *supported = rnp::str_case_eq(name, "CFB"); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PK_ALG)) { + pgp_pubkey_alg_t alg = PGP_PKA_NOTHING; + *supported = str_to_pubkey_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_HASH_ALG)) { + pgp_hash_alg_t alg = PGP_HASH_UNKNOWN; + *supported = str_to_hash_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_COMP_ALG)) { + pgp_compression_type_t alg = PGP_C_UNKNOWN; + *supported = str_to_compression_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_CURVE)) { + pgp_curve_t curve = PGP_CURVE_UNKNOWN; + *supported = curve_str_to_type(name, &curve); + } else { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +json_array_add_id_str(json_object *arr, const id_str_pair *map, bool (*check)(int)) +{ + while (map->str) { + if (check(map->id) && !array_add_element_json(arr, json_object_new_string(map->str))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + map++; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_supported_features(const char *type, char **result) +try { + if (!type || !result) { + return RNP_ERROR_NULL_POINTER; + } + + json_object *features = json_object_new_array(); + if (!features) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + if (rnp::str_case_eq(type, RNP_FEATURE_SYMM_ALG)) { + ret = json_array_add_id_str(features, symm_alg_map, symm_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_AEAD_ALG)) { + ret = json_array_add_id_str(features, aead_alg_map, aead_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PROT_MODE)) { + ret = json_array_add_id_str( + features, cipher_mode_map, [](int alg) { return alg == PGP_CIPHER_MODE_CFB; }); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PK_ALG)) { + ret = json_array_add_id_str(features, pubkey_alg_map, pub_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_HASH_ALG)) { + ret = json_array_add_id_str(features, hash_alg_map, hash_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_COMP_ALG)) { + ret = json_array_add_id_str(features, compress_alg_map, z_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_CURVE)) { + for (pgp_curve_t curve = PGP_CURVE_NIST_P_256; curve < PGP_CURVE_MAX; + curve = (pgp_curve_t)(curve + 1)) { + const ec_curve_desc_t *desc = get_curve_desc(curve); + if (!desc) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + if (!desc->supported) { + continue; + } + if (!array_add_element_json(features, json_object_new_string(desc->pgp_name))) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + ret = RNP_SUCCESS; + } + + if (ret) { + goto done; + } + + *result = (char *) json_object_to_json_string_ext(features, JSON_C_TO_STRING_PRETTY); + if (!*result) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + *result = strdup(*result); + if (!*result) { + ret = RNP_ERROR_OUT_OF_MEMORY; + } +done: + json_object_put(features); + return ret; +} +FFI_GUARD + +static bool +get_feature_sec_value( + rnp_ffi_t ffi, const char *stype, const char *sname, rnp::FeatureType &type, int &value) +{ + /* check type */ + if (!rnp::str_case_eq(stype, RNP_FEATURE_HASH_ALG)) { + FFI_LOG(ffi, "Unsupported feature type: %s", stype); + return false; + } + type = rnp::FeatureType::Hash; + /* check feature name */ + pgp_hash_alg_t alg = PGP_HASH_UNKNOWN; + if (sname && !str_to_hash_alg(sname, &alg)) { + FFI_LOG(ffi, "Unknown hash algorithm: %s", sname); + return false; + } + value = alg; + return true; +} + +static bool +get_feature_sec_level(rnp_ffi_t ffi, uint32_t flevel, rnp::SecurityLevel &level) +{ + switch (flevel) { + case RNP_SECURITY_PROHIBITED: + level = rnp::SecurityLevel::Disabled; + break; + case RNP_SECURITY_INSECURE: + level = rnp::SecurityLevel::Insecure; + break; + case RNP_SECURITY_DEFAULT: + level = rnp::SecurityLevel::Default; + break; + default: + FFI_LOG(ffi, "Invalid security level : %" PRIu32, flevel); + return false; + } + return true; +} + +static bool +extract_flag(uint32_t &flags, uint32_t flag) +{ + bool res = flags & flag; + flags &= ~flag; + return res; +} + +rnp_result_t +rnp_add_security_rule(rnp_ffi_t ffi, + const char *type, + const char *name, + uint32_t flags, + uint64_t from, + uint32_t level) +try { + if (!ffi || !type || !name) { + return RNP_ERROR_NULL_POINTER; + } + /* convert values */ + rnp::FeatureType ftype; + int fvalue; + rnp::SecurityLevel sec_level; + if (!get_feature_sec_value(ffi, type, name, ftype, fvalue) || + !get_feature_sec_level(ffi, level, sec_level)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* check flags */ + bool rule_override = extract_flag(flags, RNP_SECURITY_OVERRIDE); + bool verify_key = extract_flag(flags, RNP_SECURITY_VERIFY_KEY); + bool verify_data = extract_flag(flags, RNP_SECURITY_VERIFY_DATA); + if (flags) { + FFI_LOG(ffi, "Unknown flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* add rule */ + rnp::SecurityRule newrule(ftype, fvalue, sec_level, from); + newrule.override = rule_override; + /* Add rule for any action */ + if (!verify_key && !verify_data) { + ffi->profile().add_rule(newrule); + return RNP_SUCCESS; + } + /* Add rule for each specified key usage */ + if (verify_key) { + newrule.action = rnp::SecurityAction::VerifyKey; + ffi->profile().add_rule(newrule); + } + if (verify_data) { + newrule.action = rnp::SecurityAction::VerifyData; + ffi->profile().add_rule(newrule); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp::SecurityAction +get_security_action(uint32_t flags) +{ + if (flags & RNP_SECURITY_VERIFY_KEY) { + return rnp::SecurityAction::VerifyKey; + } + if (flags & RNP_SECURITY_VERIFY_DATA) { + return rnp::SecurityAction::VerifyData; + } + return rnp::SecurityAction::Any; +} + +rnp_result_t +rnp_get_security_rule(rnp_ffi_t ffi, + const char *type, + const char *name, + uint64_t time, + uint32_t * flags, + uint64_t * from, + uint32_t * level) +try { + if (!ffi || !type || !name || !level) { + return RNP_ERROR_NULL_POINTER; + } + /* convert values */ + rnp::FeatureType ftype; + int fvalue; + if (!get_feature_sec_value(ffi, type, name, ftype, fvalue)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* init default rule */ + rnp::SecurityRule rule(ftype, fvalue, ffi->profile().def_level()); + /* Check whether limited usage is requested */ + auto action = get_security_action(flags ? *flags : 0); + /* check whether rule exists */ + if (ffi->profile().has_rule(ftype, fvalue, time, action)) { + rule = ffi->profile().get_rule(ftype, fvalue, time, action); + } + /* fill the results */ + if (flags) { + *flags = rule.override ? RNP_SECURITY_OVERRIDE : 0; + switch (rule.action) { + case rnp::SecurityAction::VerifyKey: + *flags |= RNP_SECURITY_VERIFY_KEY; + break; + case rnp::SecurityAction::VerifyData: + *flags |= RNP_SECURITY_VERIFY_DATA; + break; + default: + break; + } + } + if (from) { + *from = rule.from; + } + switch (rule.level) { + case rnp::SecurityLevel::Disabled: + *level = RNP_SECURITY_PROHIBITED; + break; + case rnp::SecurityLevel::Insecure: + *level = RNP_SECURITY_INSECURE; + break; + case rnp::SecurityLevel::Default: + *level = RNP_SECURITY_DEFAULT; + break; + default: + FFI_LOG(ffi, "Invalid security level."); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_remove_security_rule(rnp_ffi_t ffi, + const char *type, + const char *name, + uint32_t level, + uint32_t flags, + uint64_t from, + size_t * removed) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + /* check flags */ + bool remove_all = extract_flag(flags, RNP_SECURITY_REMOVE_ALL); + bool rule_override = extract_flag(flags, RNP_SECURITY_OVERRIDE); + rnp::SecurityAction action = get_security_action(flags); + extract_flag(flags, RNP_SECURITY_VERIFY_DATA | RNP_SECURITY_VERIFY_KEY); + if (flags) { + FFI_LOG(ffi, "Unknown flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* remove all rules */ + size_t rules = ffi->profile().size(); + if (!type) { + ffi->profile().clear_rules(); + goto success; + } + rnp::FeatureType ftype; + int fvalue; + rnp::SecurityLevel flevel; + if (!get_feature_sec_value(ffi, type, name, ftype, fvalue) || + !get_feature_sec_level(ffi, level, flevel)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* remove all rules for the specified type */ + if (!name) { + ffi->profile().clear_rules(ftype); + goto success; + } + if (remove_all) { + /* remove all rules for the specified type and name */ + ffi->profile().clear_rules(ftype, fvalue); + } else { + /* remove specific rule */ + rnp::SecurityRule rule(ftype, fvalue, flevel, from, action); + rule.override = rule_override; + ffi->profile().del_rule(rule); + } +success: + if (removed) { + *removed = rules - ffi->profile().size(); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_request_password(rnp_ffi_t ffi, rnp_key_handle_t key, const char *context, char **password) +try { + if (!ffi || !password || !ffi->getpasscb) { + return RNP_ERROR_NULL_POINTER; + } + + rnp::secure_vector<char> pass(MAX_PASSWORD_LENGTH, '\0'); + bool req_res = + ffi->getpasscb(ffi, ffi->getpasscb_ctx, key, context, pass.data(), pass.size()); + if (!req_res) { + return RNP_ERROR_GENERIC; + } + size_t pass_len = strlen(pass.data()) + 1; + *password = (char *) malloc(pass_len); + if (!*password) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*password, pass.data(), pass_len); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_set_timestamp(rnp_ffi_t ffi, uint64_t time) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + ffi->context.set_time(time); + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +load_keys_from_input(rnp_ffi_t ffi, rnp_input_t input, rnp_key_store_t *store) +{ + pgp_key_provider_t chained(rnp_key_provider_store, store); + const pgp_key_provider_t *key_providers[] = {&chained, &ffi->key_provider, NULL}; + const pgp_key_provider_t key_provider(rnp_key_provider_chained, key_providers); + + if (!input->src_directory.empty()) { + // load the keys + store->path = input->src_directory; + if (!rnp_key_store_load_from_path(store, &key_provider)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; + } + + // load the keys + if (!rnp_key_store_load_from_src(store, &input->src, &key_provider)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +static bool +key_needs_conversion(const pgp_key_t *key, const rnp_key_store_t *store) +{ + pgp_key_store_format_t key_format = key->format; + pgp_key_store_format_t store_format = store->format; + /* pgp_key_t->format is only ever GPG or G10. + * + * The key store, however, could have a format of KBX, GPG, or G10. + * A KBX (and GPG) key store can only handle a pgp_key_t with a format of GPG. + * A G10 key store can only handle a pgp_key_t with a format of G10. + */ + // should never be the case + assert(key_format != PGP_KEY_STORE_KBX); + // normalize the store format + if (store_format == PGP_KEY_STORE_KBX) { + store_format = PGP_KEY_STORE_GPG; + } + // from here, both the key and store formats can only be GPG or G10 + return key_format != store_format; +} + +static rnp_result_t +do_load_keys(rnp_ffi_t ffi, + rnp_input_t input, + pgp_key_store_format_t format, + key_type_t key_type) +{ + // create a temporary key store to hold the keys + std::unique_ptr<rnp_key_store_t> tmp_store; + try { + tmp_store = + std::unique_ptr<rnp_key_store_t>(new rnp_key_store_t(format, "", ffi->context)); + } catch (const std::invalid_argument &e) { + FFI_LOG(ffi, "Failed to create key store of format: %d", (int) format); + return RNP_ERROR_BAD_PARAMETERS; + } + + // load keys into our temporary store + rnp_result_t tmpret = load_keys_from_input(ffi, input, tmp_store.get()); + if (tmpret) { + return tmpret; + } + // go through all the loaded keys + for (auto &key : tmp_store->keys) { + // check that the key is the correct type and has not already been loaded + // add secret key part if it is and we need it + if (key.is_secret() && ((key_type == KEY_TYPE_SECRET) || (key_type == KEY_TYPE_ANY))) { + if (key_needs_conversion(&key, ffi->secring)) { + FFI_LOG(ffi, "This key format conversion is not yet supported"); + return RNP_ERROR_NOT_IMPLEMENTED; + } + + if (!rnp_key_store_add_key(ffi->secring, &key)) { + FFI_LOG(ffi, "Failed to add secret key"); + return RNP_ERROR_GENERIC; + } + } + + // add public key part if needed + if ((key.format == PGP_KEY_STORE_G10) || + ((key_type != KEY_TYPE_ANY) && (key_type != KEY_TYPE_PUBLIC))) { + continue; + } + + pgp_key_t keycp; + try { + keycp = pgp_key_t(key, true); + } catch (const std::exception &e) { + RNP_LOG("Failed to copy public key part: %s", e.what()); + return RNP_ERROR_GENERIC; + } + + /* TODO: We could do this a few different ways. There isn't an obvious reason + * to restrict what formats we load, so we don't necessarily need to require a + * conversion just to load and use a G10 key when using GPG keyrings, for + * example. We could just convert when saving. + */ + + if (key_needs_conversion(&key, ffi->pubring)) { + FFI_LOG(ffi, "This key format conversion is not yet supported"); + return RNP_ERROR_NOT_IMPLEMENTED; + } + + if (!rnp_key_store_add_key(ffi->pubring, &keycp)) { + FFI_LOG(ffi, "Failed to add public key"); + return RNP_ERROR_GENERIC; + } + } + // success, even if we didn't actually load any + return RNP_SUCCESS; +} + +static key_type_t +flags_to_key_type(uint32_t *flags) +{ + key_type_t type = KEY_TYPE_NONE; + // figure out what type of keys to operate on, based on flags + if ((*flags & RNP_LOAD_SAVE_PUBLIC_KEYS) && (*flags & RNP_LOAD_SAVE_SECRET_KEYS)) { + type = KEY_TYPE_ANY; + extract_flag(*flags, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS); + } else if (*flags & RNP_LOAD_SAVE_PUBLIC_KEYS) { + type = KEY_TYPE_PUBLIC; + extract_flag(*flags, RNP_LOAD_SAVE_PUBLIC_KEYS); + } else if (*flags & RNP_LOAD_SAVE_SECRET_KEYS) { + type = KEY_TYPE_SECRET; + extract_flag(*flags, RNP_LOAD_SAVE_SECRET_KEYS); + } + return type; +} + +rnp_result_t +rnp_load_keys(rnp_ffi_t ffi, const char *format, rnp_input_t input, uint32_t flags) +try { + // checks + if (!ffi || !format || !input) { + return RNP_ERROR_NULL_POINTER; + } + key_type_t type = flags_to_key_type(&flags); + if (!type) { + FFI_LOG(ffi, "invalid flags - must have public and/or secret keys"); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_store_format_t ks_format = PGP_KEY_STORE_UNKNOWN; + if (!parse_ks_format(&ks_format, format)) { + FFI_LOG(ffi, "invalid key store format: %s", format); + return RNP_ERROR_BAD_PARAMETERS; + } + + // check for any unrecognized flags (not forward-compat, but maybe still a good idea) + if (flags) { + FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + return do_load_keys(ffi, input, ks_format, type); +} +FFI_GUARD + +rnp_result_t +rnp_unload_keys(rnp_ffi_t ffi, uint32_t flags) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + + if (flags & ~(RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (flags & RNP_KEY_UNLOAD_PUBLIC) { + rnp_key_store_clear(ffi->pubring); + } + if (flags & RNP_KEY_UNLOAD_SECRET) { + rnp_key_store_clear(ffi->secring); + } + + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_input_dearmor_if_needed(rnp_input_t input, bool noheaders = false) +{ + if (!input) { + return RNP_ERROR_NULL_POINTER; + } + if (!input->src_directory.empty()) { + return RNP_ERROR_BAD_PARAMETERS; + } + bool require_armor = false; + /* check whether we already have armored stream */ + if (input->src.type == PGP_STREAM_ARMORED) { + if (!src_eof(&input->src)) { + /* be ready for the case of damaged armoring */ + return src_error(&input->src) ? RNP_ERROR_READ : RNP_SUCCESS; + } + /* eof - probably next we have another armored message */ + src_close(&input->src); + rnp_input_st *base = (rnp_input_st *) input->app_ctx; + *input = std::move(*base); + delete base; + /* we should not mix armored data with binary */ + require_armor = true; + } + if (src_eof(&input->src)) { + return RNP_ERROR_EOF; + } + /* check whether input is armored only if base64 is not forced */ + if (!noheaders && !is_armored_source(&input->src)) { + return require_armor ? RNP_ERROR_BAD_FORMAT : RNP_SUCCESS; + } + + /* Store original input in app_ctx and replace src/app_ctx with armored data */ + rnp_input_t app_ctx = new rnp_input_st(); + *app_ctx = std::move(*input); + + rnp_result_t ret = init_armored_src(&input->src, &app_ctx->src, noheaders); + if (ret) { + /* original src may be changed during init_armored_src call, so copy it back */ + *input = std::move(*app_ctx); + delete app_ctx; + return ret; + } + input->app_ctx = app_ctx; + return RNP_SUCCESS; +} + +static const char * +key_status_to_str(pgp_key_import_status_t status) +{ + if (status == PGP_KEY_IMPORT_STATUS_UNKNOWN) { + return "none"; + } + return id_str_pair::lookup(key_import_status_map, status, "none"); +} + +static rnp_result_t +add_key_status(json_object * keys, + const pgp_key_t * key, + pgp_key_import_status_t pub, + pgp_key_import_status_t sec) +{ + json_object *jsokey = json_object_new_object(); + if (!jsokey) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (!obj_add_field_json( + jsokey, "public", json_object_new_string(key_status_to_str(pub))) || + !obj_add_field_json( + jsokey, "secret", json_object_new_string(key_status_to_str(sec))) || + !obj_add_hex_json(jsokey, "fingerprint", key->fp().fingerprint, key->fp().length) || + !array_add_element_json(keys, jsokey)) { + json_object_put(jsokey); + return RNP_ERROR_OUT_OF_MEMORY; + } + + return RNP_SUCCESS; +} + +rnp_result_t +rnp_import_keys(rnp_ffi_t ffi, rnp_input_t input, uint32_t flags, char **results) +try { + if (!ffi || !input) { + return RNP_ERROR_NULL_POINTER; + } + bool sec = extract_flag(flags, RNP_LOAD_SAVE_SECRET_KEYS); + bool pub = extract_flag(flags, RNP_LOAD_SAVE_PUBLIC_KEYS); + if (!pub && !sec) { + FFI_LOG(ffi, "bad flags: need to specify public and/or secret keys"); + return RNP_ERROR_BAD_PARAMETERS; + } + bool skipbad = extract_flag(flags, RNP_LOAD_SAVE_PERMISSIVE); + bool single = extract_flag(flags, RNP_LOAD_SAVE_SINGLE); + bool base64 = extract_flag(flags, RNP_LOAD_SAVE_BASE64); + if (flags) { + FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + rnp_key_store_t tmp_store(PGP_KEY_STORE_GPG, "", ffi->context); + + /* check whether input is base64 */ + if (base64 && is_base64_source(input->src)) { + ret = rnp_input_dearmor_if_needed(input, true); + if (ret) { + return ret; + } + } + + // load keys to temporary keystore. + if (single) { + /* we need to init and handle dearmor on this layer since it may be used for the next + * keys import */ + ret = rnp_input_dearmor_if_needed(input); + if (ret == RNP_ERROR_EOF) { + return ret; + } + if (ret) { + FFI_LOG(ffi, "Failed to init/check dearmor."); + return ret; + } + ret = rnp_key_store_pgp_read_key_from_src(tmp_store, input->src, skipbad); + if (ret) { + return ret; + } + } else { + ret = rnp_key_store_pgp_read_from_src(&tmp_store, &input->src, skipbad); + if (ret) { + return ret; + } + } + + json_object *jsores = json_object_new_object(); + if (!jsores) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp::JSONObject jsowrap(jsores); + json_object * jsokeys = json_object_new_array(); + if (!obj_add_field_json(jsores, "keys", jsokeys)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + // import keys to the main keystore. + for (auto &key : tmp_store.keys) { + pgp_key_import_status_t pub_status = PGP_KEY_IMPORT_STATUS_UNKNOWN; + pgp_key_import_status_t sec_status = PGP_KEY_IMPORT_STATUS_UNKNOWN; + if (!pub && key.is_public()) { + continue; + } + // if we got here then we add public key itself or public part of the secret key + if (!rnp_key_store_import_key(ffi->pubring, &key, true, &pub_status)) { + return RNP_ERROR_BAD_PARAMETERS; + } + // import secret key part if available and requested + if (sec && key.is_secret()) { + if (!rnp_key_store_import_key(ffi->secring, &key, false, &sec_status)) { + return RNP_ERROR_BAD_PARAMETERS; + } + // add uids, certifications and other stuff from the public key if any + pgp_key_t *expub = rnp_key_store_get_key_by_fpr(ffi->pubring, key.fp()); + if (expub && !rnp_key_store_import_key(ffi->secring, expub, true, NULL)) { + return RNP_ERROR_BAD_PARAMETERS; + } + } + // now add key fingerprint to json based on statuses + rnp_result_t tmpret = add_key_status(jsokeys, &key, pub_status, sec_status); + if (tmpret) { + return tmpret; + } + } + + if (results) { + *results = (char *) json_object_to_json_string_ext(jsores, JSON_C_TO_STRING_PRETTY); + if (!*results) { + return RNP_ERROR_GENERIC; + } + *results = strdup(*results); + if (!*results) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} +FFI_GUARD + +static const char * +sig_status_to_str(pgp_sig_import_status_t status) +{ + if (status == PGP_SIG_IMPORT_STATUS_UNKNOWN) { + return "none"; + } + return id_str_pair::lookup(sig_import_status_map, status, "none"); +} + +static rnp_result_t +add_sig_status(json_object * sigs, + const pgp_key_t * signer, + pgp_sig_import_status_t pub, + pgp_sig_import_status_t sec) +{ + json_object *jsosig = json_object_new_object(); + if (!jsosig) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (!obj_add_field_json( + jsosig, "public", json_object_new_string(sig_status_to_str(pub))) || + !obj_add_field_json( + jsosig, "secret", json_object_new_string(sig_status_to_str(sec)))) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (signer) { + const pgp_fingerprint_t &fp = signer->fp(); + if (!obj_add_hex_json(jsosig, "signer fingerprint", fp.fingerprint, fp.length)) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + + if (!array_add_element_json(sigs, jsosig)) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + + return RNP_SUCCESS; +} + +rnp_result_t +rnp_import_signatures(rnp_ffi_t ffi, rnp_input_t input, uint32_t flags, char **results) +try { + if (!ffi || !input) { + return RNP_ERROR_NULL_POINTER; + } + if (flags) { + FFI_LOG(ffi, "wrong flags: %d", (int) flags); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_signature_list_t sigs; + rnp_result_t sigret = process_pgp_signatures(input->src, sigs); + if (sigret) { + FFI_LOG(ffi, "failed to parse signature(s)"); + return sigret; + } + + json_object *jsores = json_object_new_object(); + if (!jsores) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp::JSONObject jsowrap(jsores); + json_object * jsosigs = json_object_new_array(); + if (!obj_add_field_json(jsores, "sigs", jsosigs)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + for (auto &sig : sigs) { + pgp_sig_import_status_t pub_status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + pgp_sig_import_status_t sec_status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + pgp_key_t *pkey = rnp_key_store_import_signature(ffi->pubring, &sig, &pub_status); + pgp_key_t *skey = rnp_key_store_import_signature(ffi->secring, &sig, &sec_status); + sigret = add_sig_status(jsosigs, pkey ? pkey : skey, pub_status, sec_status); + if (sigret) { + return sigret; + } + } + + if (results) { + *results = (char *) json_object_to_json_string_ext(jsores, JSON_C_TO_STRING_PRETTY); + if (!*results) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *results = strdup(*results); + if (!*results) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +copy_store_keys(rnp_ffi_t ffi, rnp_key_store_t *dest, rnp_key_store_t *src) +{ + for (auto &key : src->keys) { + if (!rnp_key_store_add_key(dest, &key)) { + FFI_LOG(ffi, "failed to add key to the store"); + return false; + } + } + return true; +} + +static rnp_result_t +do_save_keys(rnp_ffi_t ffi, + rnp_output_t output, + pgp_key_store_format_t format, + key_type_t key_type) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + + // create a temporary key store to hold the keys + rnp_key_store_t *tmp_store = NULL; + try { + tmp_store = new rnp_key_store_t(format, "", ffi->context); + } catch (const std::invalid_argument &e) { + FFI_LOG(ffi, "Failed to create key store of format: %d", (int) format); + return RNP_ERROR_BAD_PARAMETERS; + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + // include the public keys, if desired + if (key_type == KEY_TYPE_PUBLIC || key_type == KEY_TYPE_ANY) { + if (!copy_store_keys(ffi, tmp_store, ffi->pubring)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + // include the secret keys, if desired + if (key_type == KEY_TYPE_SECRET || key_type == KEY_TYPE_ANY) { + if (!copy_store_keys(ffi, tmp_store, ffi->secring)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + // preliminary check on the format + for (auto &key : tmp_store->keys) { + if (key_needs_conversion(&key, tmp_store)) { + FFI_LOG(ffi, "This key format conversion is not yet supported"); + ret = RNP_ERROR_NOT_IMPLEMENTED; + goto done; + } + } + // write + if (output->dst_directory) { + try { + tmp_store->path = output->dst_directory; + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + if (!rnp_key_store_write_to_path(tmp_store)) { + ret = RNP_ERROR_WRITE; + goto done; + } + ret = RNP_SUCCESS; + } else { + if (!rnp_key_store_write_to_dst(tmp_store, &output->dst)) { + ret = RNP_ERROR_WRITE; + goto done; + } + dst_flush(&output->dst); + output->keep = (output->dst.werr == RNP_SUCCESS); + ret = output->dst.werr; + } + +done: + delete tmp_store; + return ret; +} + +rnp_result_t +rnp_save_keys(rnp_ffi_t ffi, const char *format, rnp_output_t output, uint32_t flags) +try { + // checks + if (!ffi || !format || !output) { + return RNP_ERROR_NULL_POINTER; + } + key_type_t type = flags_to_key_type(&flags); + if (!type) { + FFI_LOG(ffi, "invalid flags - must have public and/or secret keys"); + return RNP_ERROR_BAD_PARAMETERS; + } + // check for any unrecognized flags (not forward-compat, but maybe still a good idea) + if (flags) { + FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_store_format_t ks_format = PGP_KEY_STORE_UNKNOWN; + if (!parse_ks_format(&ks_format, format)) { + FFI_LOG(ffi, "unknown key store format: %s", format); + return RNP_ERROR_BAD_PARAMETERS; + } + return do_save_keys(ffi, output, ks_format, type); +} +FFI_GUARD + +rnp_result_t +rnp_get_public_key_count(rnp_ffi_t ffi, size_t *count) +try { + if (!ffi || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = rnp_key_store_get_key_count(ffi->pubring); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_get_secret_key_count(rnp_ffi_t ffi, size_t *count) +try { + if (!ffi || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = rnp_key_store_get_key_count(ffi->secring); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_input_st::rnp_input_st() : reader(NULL), closer(NULL), app_ctx(NULL) +{ + memset(&src, 0, sizeof(src)); +} + +rnp_input_st & +rnp_input_st::operator=(rnp_input_st &&input) +{ + src_close(&src); + src = std::move(input.src); + memset(&input.src, 0, sizeof(input.src)); + reader = input.reader; + input.reader = NULL; + closer = input.closer; + input.closer = NULL; + app_ctx = input.app_ctx; + input.app_ctx = NULL; + src_directory = std::move(input.src_directory); + return *this; +} + +rnp_input_st::~rnp_input_st() +{ + bool armored = src.type == PGP_STREAM_ARMORED; + src_close(&src); + if (armored) { + rnp_input_t armored = (rnp_input_t) app_ctx; + delete armored; + app_ctx = NULL; + } +} + +rnp_result_t +rnp_input_from_path(rnp_input_t *input, const char *path) +try { + if (!input || !path) { + return RNP_ERROR_NULL_POINTER; + } + rnp_input_st *ob = new rnp_input_st(); + struct stat st = {0}; + if (rnp_stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + // a bit hacky, just save the directory path + ob->src_directory = path; + // return error on attempt to read from this source + (void) init_null_src(&ob->src); + } else { + // simple input from a file + rnp_result_t ret = init_file_src(&ob->src, path); + if (ret) { + delete ob; + return ret; + } + } + *input = ob; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_input_from_stdin(rnp_input_t *input) +try { + if (!input) { + return RNP_ERROR_NULL_POINTER; + } + *input = new rnp_input_st(); + rnp_result_t ret = init_stdin_src(&(*input)->src); + if (ret) { + delete *input; + *input = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_input_from_memory(rnp_input_t *input, const uint8_t buf[], size_t buf_len, bool do_copy) +try { + if (!input || !buf) { + return RNP_ERROR_NULL_POINTER; + } + if (!buf_len) { + return RNP_ERROR_SHORT_BUFFER; + } + *input = new rnp_input_st(); + uint8_t *data = (uint8_t *) buf; + if (do_copy) { + data = (uint8_t *) malloc(buf_len); + if (!data) { + delete *input; + *input = NULL; + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(data, buf, buf_len); + } + rnp_result_t ret = init_mem_src(&(*input)->src, data, buf_len, do_copy); + if (ret) { + if (do_copy) { + free(data); + } + delete *input; + *input = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +input_reader_bounce(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + rnp_input_t input = (rnp_input_t) src->param; + if (!input->reader) { + return false; + } + return input->reader(input->app_ctx, buf, len, read); +} + +static void +input_closer_bounce(pgp_source_t *src) +{ + rnp_input_t input = (rnp_input_t) src->param; + if (input->closer) { + input->closer(input->app_ctx); + } +} + +rnp_result_t +rnp_input_from_callback(rnp_input_t * input, + rnp_input_reader_t *reader, + rnp_input_closer_t *closer, + void * app_ctx) +try { + // checks + if (!input || !reader) { + return RNP_ERROR_NULL_POINTER; + } + rnp_input_st *obj = new rnp_input_st(); + pgp_source_t *src = &obj->src; + obj->reader = reader; + obj->closer = closer; + obj->app_ctx = app_ctx; + if (!init_src_common(src, 0)) { + delete obj; + return RNP_ERROR_OUT_OF_MEMORY; + } + src->param = obj; + src->read = input_reader_bounce; + src->close = input_closer_bounce; + src->type = PGP_STREAM_MEMORY; + *input = obj; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_input_destroy(rnp_input_t input) +try { + delete input; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_path(rnp_output_t *output, const char *path) +try { + struct rnp_output_st *ob = NULL; + struct stat st = {0}; + + if (!output || !path) { + return RNP_ERROR_NULL_POINTER; + } + ob = (rnp_output_st *) calloc(1, sizeof(*ob)); + if (!ob) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (rnp_stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + // a bit hacky, just save the directory path + ob->dst_directory = strdup(path); + if (!ob->dst_directory) { + free(ob); + return RNP_ERROR_OUT_OF_MEMORY; + } + } else { + // simple output to a file + rnp_result_t ret = init_file_dest(&ob->dst, path, true); + if (ret) { + free(ob); + return ret; + } + } + *output = ob; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_file(rnp_output_t *output, const char *path, uint32_t flags) +try { + if (!output || !path) { + return RNP_ERROR_NULL_POINTER; + } + bool overwrite = extract_flag(flags, RNP_OUTPUT_FILE_OVERWRITE); + bool random = extract_flag(flags, RNP_OUTPUT_FILE_RANDOM); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_output_t res = (rnp_output_t) calloc(1, sizeof(*res)); + if (!res) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = RNP_ERROR_GENERIC; + if (random) { + ret = init_tmpfile_dest(&res->dst, path, overwrite); + } else { + ret = init_file_dest(&res->dst, path, overwrite); + } + if (ret) { + free(res); + return ret; + } + *output = res; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_stdout(rnp_output_t *output) +try { + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + rnp_output_t res = (rnp_output_t) calloc(1, sizeof(*res)); + if (!res) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_stdout_dest(&res->dst); + if (ret) { + free(res); + return ret; + } + *output = res; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_memory(rnp_output_t *output, size_t max_alloc) +try { + // checks + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_mem_dest(&(*output)->dst, NULL, max_alloc); + if (ret) { + free(*output); + *output = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_armor(rnp_output_t base, rnp_output_t *output, const char *type) +try { + if (!base || !output) { + return RNP_ERROR_NULL_POINTER; + } + pgp_armored_msg_t msgtype = PGP_ARMORED_MESSAGE; + if (type) { + msgtype = static_cast<pgp_armored_msg_t>( + id_str_pair::lookup(armor_type_map, type, PGP_ARMORED_UNKNOWN)); + if (msgtype == PGP_ARMORED_UNKNOWN) { + RNP_LOG("Unsupported armor type: %s", type); + return RNP_ERROR_BAD_PARAMETERS; + } + } + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_armored_dst(&(*output)->dst, &base->dst, msgtype); + if (ret) { + free(*output); + *output = NULL; + return ret; + } + (*output)->app_ctx = base; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_memory_get_buf(rnp_output_t output, uint8_t **buf, size_t *len, bool do_copy) +try { + if (!output || !buf || !len) { + return RNP_ERROR_NULL_POINTER; + } + + *len = output->dst.writeb; + *buf = (uint8_t *) mem_dest_get_memory(&output->dst); + if (!*buf) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (do_copy) { + uint8_t *tmp_buf = *buf; + *buf = (uint8_t *) malloc(*len); + if (!*buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*buf, tmp_buf, *len); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +output_writer_bounce(pgp_dest_t *dst, const void *buf, size_t len) +{ + rnp_output_t output = (rnp_output_t) dst->param; + if (!output->writer) { + return RNP_ERROR_NULL_POINTER; + } + if (!output->writer(output->app_ctx, buf, len)) { + return RNP_ERROR_WRITE; + } + return RNP_SUCCESS; +} + +static void +output_closer_bounce(pgp_dest_t *dst, bool discard) +{ + rnp_output_t output = (rnp_output_t) dst->param; + if (output->closer) { + output->closer(output->app_ctx, discard); + } +} + +rnp_result_t +rnp_output_to_null(rnp_output_t *output) +try { + // checks + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_null_dest(&(*output)->dst); + if (ret) { + free(*output); + *output = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_write(rnp_output_t output, const void *data, size_t size, size_t *written) +try { + if (!output || (!data && size)) { + return RNP_ERROR_NULL_POINTER; + } + if (!data && !size) { + if (written) { + *written = 0; + } + return RNP_SUCCESS; + } + size_t old = output->dst.writeb + output->dst.clen; + dst_write(&output->dst, data, size); + if (!output->dst.werr && written) { + *written = output->dst.writeb + output->dst.clen - old; + } + output->keep = !output->dst.werr; + return output->dst.werr; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_callback(rnp_output_t * output, + rnp_output_writer_t *writer, + rnp_output_closer_t *closer, + void * app_ctx) +try { + // checks + if (!output || !writer) { + return RNP_ERROR_NULL_POINTER; + } + + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*output)->writer = writer; + (*output)->closer = closer; + (*output)->app_ctx = app_ctx; + + pgp_dest_t *dst = &(*output)->dst; + dst->write = output_writer_bounce; + dst->close = output_closer_bounce; + dst->param = *output; + dst->type = PGP_STREAM_MEMORY; + dst->writeb = 0; + dst->werr = RNP_SUCCESS; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_finish(rnp_output_t output) +try { + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + return dst_finish(&output->dst); +} +FFI_GUARD + +rnp_result_t +rnp_output_destroy(rnp_output_t output) +try { + if (output) { + if (output->dst.type == PGP_STREAM_ARMORED) { + ((rnp_output_t) output->app_ctx)->keep = output->keep; + } + dst_close(&output->dst, !output->keep); + free(output->dst_directory); + free(output); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_op_add_signature(rnp_ffi_t ffi, + rnp_op_sign_signatures_t &signatures, + rnp_key_handle_t key, + rnp_ctx_t & ctx, + rnp_op_sign_signature_t * sig) +{ + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *signkey = + find_suitable_key(PGP_OP_SIGN, get_key_require_secret(key), &key->ffi->key_provider); + if (!signkey) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + + try { + signatures.emplace_back(); + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_op_sign_signature_t newsig = &signatures.back(); + newsig->signer.key = signkey; + /* set default create/expire times */ + newsig->signer.sigcreate = ctx.sigcreate; + newsig->signer.sigexpire = ctx.sigexpire; + newsig->ffi = ffi; + + if (sig) { + *sig = newsig; + } + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_armor(rnp_ctx_t &ctx, bool armored) +{ + ctx.armor = armored; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_compression(rnp_ffi_t ffi, rnp_ctx_t &ctx, const char *compression, int level) +{ + if (!compression) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_compression_type_t zalg = PGP_C_UNKNOWN; + if (!str_to_compression_alg(compression, &zalg)) { + FFI_LOG(ffi, "Invalid compression: %s", compression); + return RNP_ERROR_BAD_PARAMETERS; + } + ctx.zalg = (int) zalg; + ctx.zlevel = level; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_hash(rnp_ffi_t ffi, rnp_ctx_t &ctx, const char *hash) +{ + if (!hash) { + return RNP_ERROR_NULL_POINTER; + } + + if (!str_to_hash_alg(hash, &ctx.halg)) { + FFI_LOG(ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_creation_time(rnp_ctx_t &ctx, uint32_t create) +{ + ctx.sigcreate = create; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_expiration_time(rnp_ctx_t &ctx, uint32_t expire) +{ + ctx.sigexpire = expire; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_flags(rnp_ffi_t ffi, rnp_ctx_t &ctx, uint32_t flags) +{ + ctx.no_wrap = extract_flag(flags, RNP_ENCRYPT_NOWRAP); + if (flags) { + FFI_LOG(ffi, "Unknown operation flags: %x", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_file_name(rnp_ctx_t &ctx, const char *filename) +{ + ctx.filename = filename ? filename : ""; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_file_mtime(rnp_ctx_t &ctx, uint32_t mtime) +{ + ctx.filemtime = mtime; + return RNP_SUCCESS; +} + +rnp_result_t +rnp_op_encrypt_create(rnp_op_encrypt_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t output) +try { + // checks + if (!op || !ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_encrypt_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->ffi = ffi; + (*op)->input = input; + (*op)->output = output; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_add_recipient(rnp_op_encrypt_t op, rnp_key_handle_t handle) +try { + // checks + if (!op || !handle) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = find_suitable_key( + PGP_OP_ENCRYPT, get_key_prefer_public(handle), &handle->ffi->key_provider); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + op->rnpctx.recipients.push_back(key); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_add_signature(rnp_op_encrypt_t op, + rnp_key_handle_t key, + rnp_op_sign_signature_t *sig) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_add_signature(op->ffi, op->signatures, key, op->rnpctx, sig); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_hash(rnp_op_encrypt_t op, const char *hash) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_hash(op->ffi, op->rnpctx, hash); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_creation_time(rnp_op_encrypt_t op, uint32_t create) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_creation_time(op->rnpctx, create); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_expiration_time(rnp_op_encrypt_t op, uint32_t expire) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_expiration_time(op->rnpctx, expire); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_add_password(rnp_op_encrypt_t op, + const char * password, + const char * s2k_hash, + size_t iterations, + const char * s2k_cipher) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (password && !*password) { + // no blank passwords + FFI_LOG(op->ffi, "Blank password"); + return RNP_ERROR_BAD_PARAMETERS; + } + + // set some defaults + if (!s2k_hash) { + s2k_hash = DEFAULT_HASH_ALG; + } + if (!s2k_cipher) { + s2k_cipher = DEFAULT_SYMM_ALG; + } + // parse + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(s2k_hash, &hash_alg)) { + FFI_LOG(op->ffi, "Invalid hash: %s", s2k_hash); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN; + if (!str_to_cipher(s2k_cipher, &symm_alg)) { + FFI_LOG(op->ffi, "Invalid cipher: %s", s2k_cipher); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp::secure_vector<char> ask_pass(MAX_PASSWORD_LENGTH, '\0'); + if (!password) { + pgp_password_ctx_t pswdctx(PGP_OP_ENCRYPT_SYM); + if (!pgp_request_password( + &op->ffi->pass_provider, &pswdctx, ask_pass.data(), ask_pass.size())) { + return RNP_ERROR_BAD_PASSWORD; + } + password = ask_pass.data(); + } + return op->rnpctx.add_encryption_password(password, hash_alg, symm_alg, iterations); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_armor(rnp_op_encrypt_t op, bool armored) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_armor(op->rnpctx, armored); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_cipher(rnp_op_encrypt_t op, const char *cipher) +try { + // checks + if (!op || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_cipher(cipher, &op->rnpctx.ealg)) { + FFI_LOG(op->ffi, "Invalid cipher: %s", cipher); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_aead(rnp_op_encrypt_t op, const char *alg) +try { + // checks + if (!op || !alg) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_aead_alg(alg, &op->rnpctx.aalg)) { + FFI_LOG(op->ffi, "Invalid AEAD algorithm: %s", alg); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_aead_bits(rnp_op_encrypt_t op, int bits) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if ((bits < 0) || (bits > 16)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->rnpctx.abits = bits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_compression(rnp_op_encrypt_t op, const char *compression, int level) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_compression(op->ffi, op->rnpctx, compression, level); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_flags(rnp_op_encrypt_t op, uint32_t flags) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_flags(op->ffi, op->rnpctx, flags); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_file_name(rnp_op_encrypt_t op, const char *filename) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_name(op->rnpctx, filename); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_file_mtime(rnp_op_encrypt_t op, uint32_t mtime) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_mtime(op->rnpctx, mtime); +} +FFI_GUARD + +static pgp_write_handler_t +pgp_write_handler(pgp_password_provider_t *pass_provider, + rnp_ctx_t * rnpctx, + void * param, + pgp_key_provider_t * key_provider) +{ + pgp_write_handler_t handler; + memset(&handler, 0, sizeof(handler)); + handler.password_provider = pass_provider; + handler.ctx = rnpctx; + handler.param = param; + handler.key_provider = key_provider; + return handler; +} + +static rnp_result_t +rnp_op_add_signatures(rnp_op_sign_signatures_t &opsigs, rnp_ctx_t &ctx) +{ + for (auto &sig : opsigs) { + if (!sig.signer.key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + + rnp_signer_info_t sinfo = sig.signer; + if (!sig.hash_set) { + sinfo.halg = ctx.halg; + } + if (!sig.expiry_set) { + sinfo.sigexpire = ctx.sigexpire; + } + if (!sig.create_set) { + sinfo.sigcreate = ctx.sigcreate; + } + ctx.signers.push_back(sinfo); + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_op_encrypt_execute(rnp_op_encrypt_t op) +try { + // checks + if (!op || !op->input || !op->output) { + return RNP_ERROR_NULL_POINTER; + } + + // set the default hash alg if none was specified + if (!op->rnpctx.halg) { + op->rnpctx.halg = DEFAULT_PGP_HASH_ALG; + } + pgp_write_handler_t handler = + pgp_write_handler(&op->ffi->pass_provider, &op->rnpctx, NULL, &op->ffi->key_provider); + + rnp_result_t ret; + if (!op->signatures.empty() && (ret = rnp_op_add_signatures(op->signatures, op->rnpctx))) { + return ret; + } + ret = rnp_encrypt_sign_src(&handler, &op->input->src, &op->output->dst); + + dst_flush(&op->output->dst); + op->output->keep = ret == RNP_SUCCESS; + op->input = NULL; + op->output = NULL; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_destroy(rnp_op_encrypt_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_create(rnp_op_sign_t *op, rnp_ffi_t ffi, rnp_input_t input, rnp_output_t output) +try { + // checks + if (!op || !ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_sign_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->ffi = ffi; + (*op)->input = input; + (*op)->output = output; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_cleartext_create(rnp_op_sign_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t output) +try { + rnp_result_t res = rnp_op_sign_create(op, ffi, input, output); + if (!res) { + (*op)->rnpctx.clearsign = true; + } + return res; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_detached_create(rnp_op_sign_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t signature) +try { + rnp_result_t res = rnp_op_sign_create(op, ffi, input, signature); + if (!res) { + (*op)->rnpctx.detached = true; + } + return res; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_add_signature(rnp_op_sign_t op, rnp_key_handle_t key, rnp_op_sign_signature_t *sig) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_add_signature(op->ffi, op->signatures, key, op->rnpctx, sig); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_signature_set_hash(rnp_op_sign_signature_t sig, const char *hash) +try { + if (!sig || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_hash_alg(hash, &sig->signer.halg)) { + FFI_LOG(sig->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + sig->hash_set = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_signature_set_creation_time(rnp_op_sign_signature_t sig, uint32_t create) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + sig->signer.sigcreate = create; + sig->create_set = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_signature_set_expiration_time(rnp_op_sign_signature_t sig, uint32_t expires) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + sig->signer.sigexpire = expires; + sig->expiry_set = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_armor(rnp_op_sign_t op, bool armored) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_armor(op->rnpctx, armored); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_compression(rnp_op_sign_t op, const char *compression, int level) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_compression(op->ffi, op->rnpctx, compression, level); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_hash(rnp_op_sign_t op, const char *hash) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_hash(op->ffi, op->rnpctx, hash); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_creation_time(rnp_op_sign_t op, uint32_t create) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_creation_time(op->rnpctx, create); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_expiration_time(rnp_op_sign_t op, uint32_t expire) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_expiration_time(op->rnpctx, expire); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_file_name(rnp_op_sign_t op, const char *filename) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_name(op->rnpctx, filename); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_file_mtime(rnp_op_sign_t op, uint32_t mtime) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_mtime(op->rnpctx, mtime); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_execute(rnp_op_sign_t op) +try { + // checks + if (!op || !op->input || !op->output) { + return RNP_ERROR_NULL_POINTER; + } + + // set the default hash alg if none was specified + if (!op->rnpctx.halg) { + op->rnpctx.halg = DEFAULT_PGP_HASH_ALG; + } + pgp_write_handler_t handler = + pgp_write_handler(&op->ffi->pass_provider, &op->rnpctx, NULL, &op->ffi->key_provider); + + rnp_result_t ret; + if ((ret = rnp_op_add_signatures(op->signatures, op->rnpctx))) { + return ret; + } + ret = rnp_sign_src(&handler, &op->input->src, &op->output->dst); + + dst_flush(&op->output->dst); + op->output->keep = ret == RNP_SUCCESS; + op->input = NULL; + op->output = NULL; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_destroy(rnp_op_sign_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +static void +rnp_op_verify_on_signatures(const std::vector<pgp_signature_info_t> &sigs, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + + try { + /* in case we have multiple signed layers */ + delete[] op->signatures; + op->signatures = new rnp_op_verify_signature_st[sigs.size()]; + } catch (const std::exception &e) { + FFI_LOG(op->ffi, "%s", e.what()); + return; + } + op->signature_count = sigs.size(); + + size_t i = 0; + for (const auto &sinfo : sigs) { + rnp_op_verify_signature_t res = &op->signatures[i++]; + /* sinfo.sig may be NULL */ + if (sinfo.sig) { + try { + res->sig_pkt = *sinfo.sig; + } catch (const std::exception &e) { + FFI_LOG(op->ffi, "%s", e.what()); + } + } + + if (sinfo.unknown) { + res->verify_status = RNP_ERROR_SIGNATURE_UNKNOWN; + } else if (sinfo.valid) { + res->verify_status = sinfo.expired ? RNP_ERROR_SIGNATURE_EXPIRED : RNP_SUCCESS; + } else { + res->verify_status = + sinfo.no_signer ? RNP_ERROR_KEY_NOT_FOUND : RNP_ERROR_SIGNATURE_INVALID; + } + res->ffi = op->ffi; + } +} + +static bool +rnp_verify_src_provider(pgp_parse_handler_t *handler, pgp_source_t *src) +{ + /* this one is called only when input for detached signature is needed */ + rnp_op_verify_t op = (rnp_op_verify_t) handler->param; + if (!op->detached_input) { + return false; + } + *src = op->detached_input->src; + /* we should give ownership on src to caller */ + memset(&op->detached_input->src, 0, sizeof(op->detached_input->src)); + return true; +}; + +static bool +rnp_verify_dest_provider(pgp_parse_handler_t *handler, + pgp_dest_t ** dst, + bool * closedst, + const char * filename, + uint32_t mtime) +{ + rnp_op_verify_t op = (rnp_op_verify_t) handler->param; + if (!op->output) { + return false; + } + *dst = &(op->output->dst); + *closedst = false; + op->filename = filename ? strdup(filename) : NULL; + op->file_mtime = mtime; + return true; +} + +static void +recipient_handle_from_pk_sesskey(rnp_recipient_handle_t handle, + const pgp_pk_sesskey_t &sesskey) +{ + static_assert(sizeof(handle->keyid) == PGP_KEY_ID_SIZE, "Keyid size mismatch"); + memcpy(handle->keyid, sesskey.key_id.data(), PGP_KEY_ID_SIZE); + handle->palg = sesskey.alg; +} + +static void +symenc_handle_from_sk_sesskey(rnp_symenc_handle_t handle, const pgp_sk_sesskey_t &sesskey) +{ + handle->alg = sesskey.alg; + handle->halg = sesskey.s2k.hash_alg; + handle->s2k_type = sesskey.s2k.specifier; + if (sesskey.s2k.specifier == PGP_S2KS_ITERATED_AND_SALTED) { + handle->iterations = pgp_s2k_decode_iterations(sesskey.s2k.iterations); + } else { + handle->iterations = 1; + } + handle->aalg = sesskey.aalg; +} + +static void +rnp_verify_on_recipients(const std::vector<pgp_pk_sesskey_t> &recipients, + const std::vector<pgp_sk_sesskey_t> &passwords, + void * param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + /* store only top-level encrypted stream recipients info for now */ + if (op->encrypted_layers++) { + return; + } + if (!recipients.empty()) { + op->recipients = + (rnp_recipient_handle_t) calloc(recipients.size(), sizeof(*op->recipients)); + if (!op->recipients) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + for (size_t i = 0; i < recipients.size(); i++) { + recipient_handle_from_pk_sesskey(&op->recipients[i], recipients[i]); + } + } + op->recipient_count = recipients.size(); + if (!passwords.empty()) { + op->symencs = (rnp_symenc_handle_t) calloc(passwords.size(), sizeof(*op->symencs)); + if (!op->symencs) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + for (size_t i = 0; i < passwords.size(); i++) { + symenc_handle_from_sk_sesskey(&op->symencs[i], passwords[i]); + } + } + op->symenc_count = passwords.size(); +} + +static void +rnp_verify_on_decryption_start(pgp_pk_sesskey_t *pubenc, pgp_sk_sesskey_t *symenc, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + /* store only top-level encrypted stream info */ + if (op->encrypted_layers > 1) { + return; + } + if (pubenc) { + op->used_recipient = (rnp_recipient_handle_t) calloc(1, sizeof(*op->used_recipient)); + if (!op->used_recipient) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + recipient_handle_from_pk_sesskey(op->used_recipient, *pubenc); + return; + } + if (symenc) { + op->used_symenc = (rnp_symenc_handle_t) calloc(1, sizeof(*op->used_symenc)); + if (!op->used_symenc) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + symenc_handle_from_sk_sesskey(op->used_symenc, *symenc); + return; + } + FFI_LOG(op->ffi, "Warning! Both pubenc and symenc are NULL."); +} + +static void +rnp_verify_on_decryption_info(bool mdc, pgp_aead_alg_t aead, pgp_symm_alg_t salg, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + /* store only top-level encrypted stream info for now */ + if (op->encrypted_layers > 1) { + return; + } + op->mdc = mdc; + op->aead = aead; + op->salg = salg; + op->encrypted = true; +} + +static void +rnp_verify_on_decryption_done(bool validated, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + if (op->encrypted_layers > 1) { + return; + } + op->validated = validated; +} + +rnp_result_t +rnp_op_verify_create(rnp_op_verify_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t output) +try { + if (!op || !ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_verify_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->ffi = ffi; + (*op)->input = input; + (*op)->output = output; + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_detached_create(rnp_op_verify_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_input_t signature) +try { + if (!op || !ffi || !input || !signature) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_verify_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->rnpctx.detached = true; + (*op)->ffi = ffi; + (*op)->input = signature; + (*op)->detached_input = input; + + return RNP_SUCCESS; +} +FFI_GUARD + +static pgp_key_t * +ffi_decrypt_key_provider(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + rnp_decryption_kp_param_t *kparam = (rnp_decryption_kp_param_t *) userdata; + + auto ffi = kparam->op->ffi; + bool hidden = ctx->secret && (ctx->search.type == PGP_KEY_SEARCH_KEYID) && + (ctx->search.by.keyid == pgp_key_id_t({})); + /* default to the FFI key provider if not hidden keyid request */ + if (!hidden) { + return ffi->key_provider.callback(ctx, ffi->key_provider.userdata); + } + /* if we had hidden request and last key is NULL then key search was exhausted */ + if (!kparam->op->allow_hidden || (kparam->has_hidden && !kparam->last)) { + return NULL; + } + /* inform user about the hidden recipient before searching through the loaded keys */ + if (!kparam->has_hidden) { + call_key_callback(ffi, ctx->search, ctx->secret); + } + kparam->has_hidden = true; + kparam->last = find_key(ffi, ctx->search, true, true, kparam->last); + return kparam->last; +} + +rnp_result_t +rnp_op_verify_set_flags(rnp_op_verify_t op, uint32_t flags) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + /* Allow to decrypt without valid signatures */ + op->ignore_sigs = extract_flag(flags, RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT); + /* Strict mode: require all signatures to be valid */ + op->require_all_sigs = extract_flag(flags, RNP_VERIFY_REQUIRE_ALL_SIGS); + /* Allow hidden recipients if any */ + op->allow_hidden = extract_flag(flags, RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT); + + if (flags) { + FFI_LOG(op->ffi, "Unknown operation flags: %x", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_execute(rnp_op_verify_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_parse_handler_t handler; + + handler.password_provider = &op->ffi->pass_provider; + + rnp_decryption_kp_param_t kparam(op); + pgp_key_provider_t kprov = {ffi_decrypt_key_provider, &kparam}; + + handler.key_provider = &kprov; + handler.on_signatures = rnp_op_verify_on_signatures; + handler.src_provider = rnp_verify_src_provider; + handler.dest_provider = rnp_verify_dest_provider; + handler.on_recipients = rnp_verify_on_recipients; + handler.on_decryption_start = rnp_verify_on_decryption_start; + handler.on_decryption_info = rnp_verify_on_decryption_info; + handler.on_decryption_done = rnp_verify_on_decryption_done; + handler.param = op; + handler.ctx = &op->rnpctx; + + rnp_result_t ret = process_pgp_source(&handler, op->input->src); + /* Allow to decrypt data ignoring the signatures check if requested */ + if (op->ignore_sigs && op->validated && (ret == RNP_ERROR_SIGNATURE_INVALID)) { + ret = RNP_SUCCESS; + } + /* Allow to require all signatures be valid */ + if (op->require_all_sigs && !ret) { + for (size_t i = 0; i < op->signature_count; i++) { + if (op->signatures[i].verify_status) { + ret = RNP_ERROR_SIGNATURE_INVALID; + break; + } + } + } + if (op->output) { + dst_flush(&op->output->dst); + op->output->keep = ret == RNP_SUCCESS; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_signature_count(rnp_op_verify_t op, size_t *count) +try { + if (!op || !count) { + return RNP_ERROR_NULL_POINTER; + } + + *count = op->signature_count; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_signature_at(rnp_op_verify_t op, size_t idx, rnp_op_verify_signature_t *sig) +try { + if (!op || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= op->signature_count) { + FFI_LOG(op->ffi, "Invalid signature index: %zu", idx); + return RNP_ERROR_BAD_PARAMETERS; + } + *sig = &op->signatures[idx]; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_file_info(rnp_op_verify_t op, char **filename, uint32_t *mtime) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (mtime) { + *mtime = op->file_mtime; + } + if (filename) { + if (op->filename) { + *filename = strdup(op->filename); + } else { + *filename = NULL; + } + } + return RNP_SUCCESS; +} +FFI_GUARD + +static const char * +get_protection_mode(rnp_op_verify_t op) +{ + if (!op->encrypted) { + return "none"; + } + if (op->mdc) { + return "cfb-mdc"; + } + if (op->aead == PGP_AEAD_NONE) { + return "cfb"; + } + switch (op->aead) { + case PGP_AEAD_EAX: + return "aead-eax"; + case PGP_AEAD_OCB: + return "aead-ocb"; + default: + return "aead-unknown"; + } +} + +static const char * +get_protection_cipher(rnp_op_verify_t op) +{ + if (!op->encrypted) { + return "none"; + } + return id_str_pair::lookup(symm_alg_map, op->salg); +} + +rnp_result_t +rnp_op_verify_get_protection_info(rnp_op_verify_t op, char **mode, char **cipher, bool *valid) +try { + if (!op || (!mode && !cipher && !valid)) { + return RNP_ERROR_NULL_POINTER; + } + + if (mode) { + *mode = strdup(get_protection_mode(op)); + if (!*mode) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + if (cipher) { + *cipher = strdup(get_protection_cipher(op)); + if (!*cipher) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + if (valid) { + *valid = op->validated; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_recipient_count(rnp_op_verify_t op, size_t *count) +try { + if (!op || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = op->recipient_count; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_used_recipient(rnp_op_verify_t op, rnp_recipient_handle_t *recipient) +try { + if (!op || !recipient) { + return RNP_ERROR_NULL_POINTER; + } + *recipient = op->used_recipient; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_recipient_at(rnp_op_verify_t op, + size_t idx, + rnp_recipient_handle_t *recipient) +try { + if (!op || !recipient) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= op->recipient_count) { + return RNP_ERROR_BAD_PARAMETERS; + } + *recipient = &op->recipients[idx]; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_recipient_get_keyid(rnp_recipient_handle_t recipient, char **keyid) +try { + if (!recipient || !keyid) { + return RNP_ERROR_NULL_POINTER; + } + static_assert(sizeof(recipient->keyid) == PGP_KEY_ID_SIZE, + "rnp_recipient_handle_t.keyid size mismatch"); + return hex_encode_value(recipient->keyid, PGP_KEY_ID_SIZE, keyid); +} +FFI_GUARD + +rnp_result_t +rnp_recipient_get_alg(rnp_recipient_handle_t recipient, char **alg) +try { + if (!recipient || !alg) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(pubkey_alg_map, recipient->palg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_symenc_count(rnp_op_verify_t op, size_t *count) +try { + if (!op || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = op->symenc_count; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_used_symenc(rnp_op_verify_t op, rnp_symenc_handle_t *symenc) +try { + if (!op || !symenc) { + return RNP_ERROR_NULL_POINTER; + } + *symenc = op->used_symenc; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_symenc_at(rnp_op_verify_t op, size_t idx, rnp_symenc_handle_t *symenc) +try { + if (!op || !symenc) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= op->symenc_count) { + return RNP_ERROR_BAD_PARAMETERS; + } + *symenc = &op->symencs[idx]; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_cipher(rnp_symenc_handle_t symenc, char **cipher) +try { + if (!symenc || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(symm_alg_map, symenc->alg, cipher); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_aead_alg(rnp_symenc_handle_t symenc, char **alg) +try { + if (!symenc || !alg) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(aead_alg_map, symenc->aalg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_hash_alg(rnp_symenc_handle_t symenc, char **alg) +try { + if (!symenc || !alg) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(hash_alg_map, symenc->halg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_s2k_type(rnp_symenc_handle_t symenc, char **type) +try { + if (!symenc || !type) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(s2k_type_map, symenc->s2k_type, type); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_s2k_iterations(rnp_symenc_handle_t symenc, uint32_t *iterations) +try { + if (!symenc || !iterations) { + return RNP_ERROR_NULL_POINTER; + } + *iterations = symenc->iterations; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_destroy(rnp_op_verify_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_op_verify_st::~rnp_op_verify_st() +{ + delete[] signatures; + free(filename); + free(recipients); + free(used_recipient); + free(symencs); + free(used_symenc); +} + +rnp_result_t +rnp_op_verify_signature_get_status(rnp_op_verify_signature_t sig) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + return sig->verify_status; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_handle(rnp_op_verify_signature_t sig, + rnp_signature_handle_t * handle) +try { + if (!sig || !handle) { + return RNP_ERROR_NULL_POINTER; + } + + *handle = (rnp_signature_handle_t) calloc(1, sizeof(**handle)); + if (!*handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + try { + (*handle)->sig = new pgp_subsig_t(sig->sig_pkt); + } catch (const std::exception &e) { + FFI_LOG(sig->ffi, "%s", e.what()); + free(*handle); + return RNP_ERROR_OUT_OF_MEMORY; + } + (*handle)->ffi = sig->ffi; + (*handle)->key = NULL; + (*handle)->own_sig = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_hash(rnp_op_verify_signature_t sig, char **hash) +try { + if (!sig || !hash) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(hash_alg_map, sig->sig_pkt.halg, hash); +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_key(rnp_op_verify_signature_t sig, rnp_key_handle_t *key) +try { + if (!sig->sig_pkt.has_keyid()) { + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_ffi_t ffi = sig->ffi; + // create a search (since we'll use this later anyways) + pgp_key_search_t search(PGP_KEY_SEARCH_KEYID); + search.by.keyid = sig->sig_pkt.keyid(); + + // search the stores + pgp_key_t *pub = rnp_key_store_search(ffi->pubring, &search, NULL); + pgp_key_t *sec = rnp_key_store_search(ffi->secring, &search, NULL); + if (!pub && !sec) { + return RNP_ERROR_KEY_NOT_FOUND; + } + + struct rnp_key_handle_st *handle = (rnp_key_handle_st *) calloc(1, sizeof(*handle)); + if (!handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + handle->ffi = ffi; + handle->pub = pub; + handle->sec = sec; + handle->locator = search; + *key = handle; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_times(rnp_op_verify_signature_t sig, + uint32_t * create, + uint32_t * expires) +try { + if (create) { + *create = sig->sig_pkt.creation(); + } + if (expires) { + *expires = sig->sig_pkt.expiration(); + } + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_decrypt(rnp_ffi_t ffi, rnp_input_t input, rnp_output_t output) +try { + // checks + if (!ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + rnp_op_verify_t op = NULL; + rnp_result_t ret = rnp_op_verify_create(&op, ffi, input, output); + if (ret) { + return ret; + } + ret = rnp_op_verify_set_flags(op, RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT); + if (!ret) { + ret = rnp_op_verify_execute(op); + } + rnp_op_verify_destroy(op); + return ret; +} +FFI_GUARD + +static rnp_result_t +str_to_locator(rnp_ffi_t ffi, + pgp_key_search_t *locator, + const char * identifier_type, + const char * identifier) +{ + // parse the identifier type + locator->type = static_cast<pgp_key_search_type_t>( + id_str_pair::lookup(identifier_type_map, identifier_type, PGP_KEY_SEARCH_UNKNOWN)); + if (locator->type == PGP_KEY_SEARCH_UNKNOWN) { + FFI_LOG(ffi, "Invalid identifier type: %s", identifier_type); + return RNP_ERROR_BAD_PARAMETERS; + } + // see what type we have + switch (locator->type) { + case PGP_KEY_SEARCH_USERID: + if (snprintf(locator->by.userid, sizeof(locator->by.userid), "%s", identifier) >= + (int) sizeof(locator->by.userid)) { + FFI_LOG(ffi, "UserID too long"); + return RNP_ERROR_BAD_PARAMETERS; + } + break; + case PGP_KEY_SEARCH_KEYID: { + if (strlen(identifier) != (PGP_KEY_ID_SIZE * 2) || + !rnp::hex_decode(identifier, locator->by.keyid.data(), locator->by.keyid.size())) { + FFI_LOG(ffi, "Invalid keyid: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + } break; + case PGP_KEY_SEARCH_FINGERPRINT: { + // TODO: support v5 fingerprints + // Note: v2/v3 fingerprint are 16 bytes (32 chars) long. + if ((strlen(identifier) != (PGP_FINGERPRINT_SIZE * 2)) && (strlen(identifier) != 32)) { + FFI_LOG(ffi, "Invalid fingerprint: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + locator->by.fingerprint.length = rnp::hex_decode( + identifier, locator->by.fingerprint.fingerprint, PGP_FINGERPRINT_SIZE); + if (!locator->by.fingerprint.length) { + FFI_LOG(ffi, "Invalid fingerprint: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + } break; + case PGP_KEY_SEARCH_GRIP: { + if (strlen(identifier) != (PGP_KEY_GRIP_SIZE * 2) || + !rnp::hex_decode(identifier, locator->by.grip.data(), locator->by.grip.size())) { + FFI_LOG(ffi, "Invalid grip: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + } break; + default: + // should never happen + assert(false); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} + +static bool +locator_to_str(const pgp_key_search_t &locator, + const char ** identifier_type, + char * identifier, + size_t identifier_size) +{ + // find the identifier type string with the map + *identifier_type = id_str_pair::lookup(identifier_type_map, locator.type, NULL); + if (!*identifier_type) { + return false; + } + // fill in the actual identifier + switch (locator.type) { + case PGP_KEY_SEARCH_USERID: + if (snprintf(identifier, identifier_size, "%s", locator.by.userid) >= + (int) identifier_size) { + return false; + } + break; + case PGP_KEY_SEARCH_KEYID: + if (!rnp::hex_encode( + locator.by.keyid.data(), locator.by.keyid.size(), identifier, identifier_size)) { + return false; + } + break; + case PGP_KEY_SEARCH_FINGERPRINT: + if (!rnp::hex_encode(locator.by.fingerprint.fingerprint, + locator.by.fingerprint.length, + identifier, + identifier_size)) { + return false; + } + break; + case PGP_KEY_SEARCH_GRIP: + if (!rnp::hex_encode( + locator.by.grip.data(), locator.by.grip.size(), identifier, identifier_size)) { + return false; + } + break; + default: + assert(false); + return false; + } + return true; +} + +static rnp_result_t +rnp_locate_key_int(rnp_ffi_t ffi, + const pgp_key_search_t &locator, + rnp_key_handle_t * handle, + bool require_secret = false) +{ + // search pubring + pgp_key_t *pub = rnp_key_store_search(ffi->pubring, &locator, NULL); + // search secring + pgp_key_t *sec = rnp_key_store_search(ffi->secring, &locator, NULL); + + if (require_secret && !sec) { + *handle = NULL; + return RNP_SUCCESS; + } + + if (pub || sec) { + *handle = (rnp_key_handle_t) malloc(sizeof(**handle)); + if (!*handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*handle)->ffi = ffi; + (*handle)->pub = pub; + (*handle)->sec = sec; + (*handle)->locator = locator; + } else { + *handle = NULL; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_locate_key(rnp_ffi_t ffi, + const char * identifier_type, + const char * identifier, + rnp_key_handle_t *handle) +try { + // checks + if (!ffi || !identifier_type || !identifier || !handle) { + return RNP_ERROR_NULL_POINTER; + } + + // figure out the identifier type + pgp_key_search_t locator; + rnp_result_t ret = str_to_locator(ffi, &locator, identifier_type, identifier); + if (ret) { + return ret; + } + + return rnp_locate_key_int(ffi, locator, handle); +} +FFI_GUARD + +rnp_result_t +rnp_key_export(rnp_key_handle_t handle, rnp_output_t output, uint32_t flags) +try { + pgp_dest_t *dst = NULL; + pgp_dest_t armordst = {}; + + // checks + if (!handle || !output) { + return RNP_ERROR_NULL_POINTER; + } + dst = &output->dst; + if ((flags & RNP_KEY_EXPORT_PUBLIC) && (flags & RNP_KEY_EXPORT_SECRET)) { + FFI_LOG(handle->ffi, "Invalid export flags, select only public or secret, not both."); + return RNP_ERROR_BAD_PARAMETERS; + } + + // handle flags + bool armored = extract_flag(flags, RNP_KEY_EXPORT_ARMORED); + pgp_key_t * key = NULL; + rnp_key_store_t *store = NULL; + if (flags & RNP_KEY_EXPORT_PUBLIC) { + extract_flag(flags, RNP_KEY_EXPORT_PUBLIC); + key = get_key_require_public(handle); + store = handle->ffi->pubring; + } else if (flags & RNP_KEY_EXPORT_SECRET) { + extract_flag(flags, RNP_KEY_EXPORT_SECRET); + key = get_key_require_secret(handle); + store = handle->ffi->secring; + } else { + FFI_LOG(handle->ffi, "must specify public or secret key for export"); + return RNP_ERROR_BAD_PARAMETERS; + } + bool export_subs = extract_flag(flags, RNP_KEY_EXPORT_SUBKEYS); + // check for any unrecognized flags + if (flags) { + FFI_LOG(handle->ffi, "unrecognized flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + // make sure we found our key + if (!key) { + FFI_LOG(handle->ffi, "no suitable key found"); + return RNP_ERROR_NO_SUITABLE_KEY; + } + // only PGP packets supported for now + if (key->format != PGP_KEY_STORE_GPG && key->format != PGP_KEY_STORE_KBX) { + return RNP_ERROR_NOT_IMPLEMENTED; + } + if (armored) { + auto msgtype = key->is_secret() ? PGP_ARMORED_SECRET_KEY : PGP_ARMORED_PUBLIC_KEY; + rnp_result_t res = init_armored_dst(&armordst, &output->dst, msgtype); + if (res) { + return res; + } + dst = &armordst; + } + // write + if (key->is_primary()) { + // primary key, write just the primary or primary and all subkeys + key->write_xfer(*dst, export_subs ? store : NULL); + if (dst->werr) { + return RNP_ERROR_WRITE; + } + } else { + // subkeys flag is only valid for primary + if (export_subs) { + FFI_LOG(handle->ffi, "export with subkeys requested but key is not primary"); + return RNP_ERROR_BAD_PARAMETERS; + } + // subkey, write the primary + this subkey only + pgp_key_t *primary = rnp_key_store_get_primary_key(store, key); + if (!primary) { + // shouldn't happen + return RNP_ERROR_GENERIC; + } + primary->write_xfer(*dst); + if (dst->werr) { + return RNP_ERROR_WRITE; + } + key->write_xfer(*dst); + if (dst->werr) { + return RNP_ERROR_WRITE; + } + } + if (armored) { + dst_finish(&armordst); + dst_close(&armordst, false); + } + output->keep = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_export_autocrypt(rnp_key_handle_t key, + rnp_key_handle_t subkey, + const char * uid, + rnp_output_t output, + uint32_t flags) +try { + if (!key || !output) { + return RNP_ERROR_NULL_POINTER; + } + bool base64 = extract_flag(flags, RNP_KEY_EXPORT_BASE64); + if (flags) { + FFI_LOG(key->ffi, "Unknown flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* Get the primary key */ + pgp_key_t *primary = get_key_prefer_public(key); + if (!primary || !primary->is_primary() || !primary->usable_for(PGP_OP_VERIFY)) { + FFI_LOG(key->ffi, "No valid signing primary key"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* Get encrypting subkey */ + pgp_key_t *sub = + subkey ? get_key_prefer_public(subkey) : + find_suitable_key(PGP_OP_ENCRYPT, primary, &key->ffi->key_provider, true); + if (!sub || sub->is_primary() || !sub->usable_for(PGP_OP_ENCRYPT)) { + FFI_LOG(key->ffi, "No encrypting subkey"); + return RNP_ERROR_KEY_NOT_FOUND; + } + /* Get userid */ + size_t uididx = primary->uid_count(); + if (uid) { + for (size_t idx = 0; idx < primary->uid_count(); idx++) { + if (primary->get_uid(idx).str == uid) { + uididx = idx; + break; + } + } + } else { + if (primary->uid_count() > 1) { + FFI_LOG(key->ffi, "Ambiguous userid"); + return RNP_ERROR_BAD_PARAMETERS; + } + uididx = 0; + } + if (uididx >= primary->uid_count()) { + FFI_LOG(key->ffi, "Userid not found"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* Check whether base64 is requested */ + bool res = false; + if (base64) { + rnp::ArmoredDest armor(output->dst, PGP_ARMORED_BASE64); + res = primary->write_autocrypt(armor.dst(), *sub, uididx); + } else { + res = primary->write_autocrypt(output->dst, *sub, uididx); + } + return res ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS; +} +FFI_GUARD + +static pgp_key_t * +rnp_key_get_revoker(rnp_key_handle_t key) +{ + pgp_key_t *exkey = get_key_prefer_public(key); + if (!exkey) { + return NULL; + } + if (exkey->is_subkey()) { + return rnp_key_store_get_primary_key(key->ffi->secring, exkey); + } + // TODO: search through revocation key subpackets as well + return get_key_require_secret(key); +} + +static rnp_result_t +rnp_key_get_revocation(rnp_ffi_t ffi, + pgp_key_t * key, + pgp_key_t * revoker, + const char * hash, + const char * code, + const char * reason, + pgp_signature_t &sig) +{ + if (!hash) { + hash = DEFAULT_HASH_ALG; + } + pgp_hash_alg_t halg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &halg)) { + FFI_LOG(ffi, "Unknown hash algorithm: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_revoke_t revinfo = {}; + if (code && !str_to_revocation_type(code, &revinfo.code)) { + FFI_LOG(ffi, "Wrong revocation code: %s", code); + return RNP_ERROR_BAD_PARAMETERS; + } + if (revinfo.code > PGP_REVOCATION_RETIRED) { + FFI_LOG(ffi, "Wrong key revocation code: %d", (int) revinfo.code); + return RNP_ERROR_BAD_PARAMETERS; + } + if (reason) { + try { + revinfo.reason = reason; + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + /* unlock the secret key if needed */ + rnp::KeyLocker revlock(*revoker); + if (revoker->is_locked() && !revoker->unlock(ffi->pass_provider)) { + FFI_LOG(ffi, "Failed to unlock secret key"); + return RNP_ERROR_BAD_PASSWORD; + } + try { + revoker->gen_revocation(revinfo, halg, key->pkt(), sig, ffi->context); + } catch (const std::exception &e) { + FFI_LOG(ffi, "Failed to generate revocation signature: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_export_revocation(rnp_key_handle_t key, + rnp_output_t output, + uint32_t flags, + const char * hash, + const char * code, + const char * reason) +try { + if (!key || !key->ffi || !output) { + return RNP_ERROR_NULL_POINTER; + } + bool need_armor = extract_flag(flags, RNP_KEY_EXPORT_ARMORED); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t *exkey = get_key_prefer_public(key); + if (!exkey || !exkey->is_primary()) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *revoker = rnp_key_get_revoker(key); + if (!revoker) { + FFI_LOG(key->ffi, "Revoker secret key not found"); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_signature_t sig; + rnp_result_t ret = + rnp_key_get_revocation(key->ffi, exkey, revoker, hash, code, reason, sig); + if (ret) { + return ret; + } + + if (need_armor) { + rnp::ArmoredDest armor(output->dst, PGP_ARMORED_PUBLIC_KEY); + sig.write(armor.dst()); + ret = armor.werr(); + dst_flush(&armor.dst()); + } else { + sig.write(output->dst); + ret = output->dst.werr; + dst_flush(&output->dst); + } + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_key_revoke( + rnp_key_handle_t key, uint32_t flags, const char *hash, const char *code, const char *reason) +try { + if (!key || !key->ffi) { + return RNP_ERROR_NULL_POINTER; + } + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t *exkey = get_key_prefer_public(key); + if (!exkey) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *revoker = rnp_key_get_revoker(key); + if (!revoker) { + FFI_LOG(key->ffi, "Revoker secret key not found"); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_signature_t sig; + rnp_result_t ret = + rnp_key_get_revocation(key->ffi, exkey, revoker, hash, code, reason, sig); + if (ret) { + return ret; + } + pgp_sig_import_status_t pub_status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY; + pgp_sig_import_status_t sec_status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY; + if (key->pub) { + pub_status = rnp_key_store_import_key_signature(key->ffi->pubring, key->pub, &sig); + } + if (key->sec) { + sec_status = rnp_key_store_import_key_signature(key->ffi->secring, key->sec, &sig); + } + + if ((pub_status == PGP_SIG_IMPORT_STATUS_UNKNOWN) || + (sec_status == PGP_SIG_IMPORT_STATUS_UNKNOWN)) { + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_25519_bits_tweaked(rnp_key_handle_t key, bool *result) +try { + if (!key || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *seckey = get_key_require_secret(key); + if (!seckey || seckey->is_locked() || (seckey->alg() != PGP_PKA_ECDH) || + (seckey->curve() != PGP_CURVE_25519)) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = x25519_bits_tweaked(seckey->material().ec); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_25519_bits_tweak(rnp_key_handle_t key) +try { + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *seckey = get_key_require_secret(key); + if (!seckey || seckey->is_protected() || (seckey->alg() != PGP_PKA_ECDH) || + (seckey->curve() != PGP_CURVE_25519)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!x25519_tweak_bits(seckey->pkt().material.ec)) { + FFI_LOG(key->ffi, "Failed to tweak 25519 key bits."); + return RNP_ERROR_BAD_STATE; + } + if (!seckey->write_sec_rawpkt(seckey->pkt(), "", key->ffi->context)) { + FFI_LOG(key->ffi, "Failed to update rawpkt."); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_remove(rnp_key_handle_t key, uint32_t flags) +try { + if (!key || !key->ffi) { + return RNP_ERROR_NULL_POINTER; + } + bool pub = extract_flag(flags, RNP_KEY_REMOVE_PUBLIC); + bool sec = extract_flag(flags, RNP_KEY_REMOVE_SECRET); + bool sub = extract_flag(flags, RNP_KEY_REMOVE_SUBKEYS); + if (flags) { + FFI_LOG(key->ffi, "Unknown flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pub && !sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (sub && get_key_prefer_public(key)->is_subkey()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (pub) { + if (!key->ffi->pubring || !key->pub) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_remove_key(key->ffi->pubring, key->pub, sub)) { + return RNP_ERROR_KEY_NOT_FOUND; + } + key->pub = NULL; + } + if (sec) { + if (!key->ffi->secring || !key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_remove_key(key->ffi->secring, key->sec, sub)) { + return RNP_ERROR_KEY_NOT_FOUND; + } + key->sec = NULL; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static void +report_signature_removal(rnp_ffi_t ffi, + const pgp_key_t & key, + rnp_key_signatures_cb sigcb, + void * app_ctx, + pgp_subsig_t & keysig, + bool & remove) +{ + if (!sigcb) { + return; + } + rnp_signature_handle_t sig = (rnp_signature_handle_t) calloc(1, sizeof(*sig)); + if (!sig) { + FFI_LOG(ffi, "Signature handle allocation failed."); + return; + } + sig->ffi = ffi; + sig->key = &key; + sig->sig = &keysig; + sig->own_sig = false; + uint32_t action = remove ? RNP_KEY_SIGNATURE_REMOVE : RNP_KEY_SIGNATURE_KEEP; + sigcb(ffi, app_ctx, sig, &action); + switch (action) { + case RNP_KEY_SIGNATURE_REMOVE: + remove = true; + break; + case RNP_KEY_SIGNATURE_KEEP: + remove = false; + break; + default: + FFI_LOG(ffi, "Invalid signature removal action: %" PRIu32, action); + break; + } + rnp_signature_handle_destroy(sig); +} + +static bool +signature_needs_removal(rnp_ffi_t ffi, const pgp_key_t &key, pgp_subsig_t &sig, uint32_t flags) +{ + /* quick check for non-self signatures */ + bool nonself = flags & RNP_KEY_SIGNATURE_NON_SELF_SIG; + if (nonself && key.is_primary() && !key.is_signer(sig)) { + return true; + } + if (nonself && key.is_subkey()) { + pgp_key_t *primary = rnp_key_store_get_primary_key(ffi->pubring, &key); + if (primary && !primary->is_signer(sig)) { + return true; + } + } + /* unknown signer */ + pgp_key_t *signer = pgp_sig_get_signer(sig, ffi->pubring, &ffi->key_provider); + if (!signer && (flags & RNP_KEY_SIGNATURE_UNKNOWN_KEY)) { + return true; + } + /* validate signature if didn't */ + if (signer && !sig.validated()) { + signer->validate_sig(key, sig, ffi->context); + } + /* we cannot check for invalid/expired if sig was not validated */ + if (!sig.validated()) { + return false; + } + if ((flags & RNP_KEY_SIGNATURE_INVALID) && !sig.validity.valid) { + return true; + } + return false; +} + +static void +remove_key_signatures(rnp_ffi_t ffi, + pgp_key_t & pub, + pgp_key_t * sec, + uint32_t flags, + rnp_key_signatures_cb sigcb, + void * app_ctx) +{ + std::vector<pgp_sig_id_t> sigs; + + for (size_t idx = 0; idx < pub.sig_count(); idx++) { + pgp_subsig_t &sig = pub.get_sig(idx); + bool remove = signature_needs_removal(ffi, pub, sig, flags); + report_signature_removal(ffi, pub, sigcb, app_ctx, sig, remove); + if (remove) { + sigs.push_back(sig.sigid); + } + } + size_t deleted = pub.del_sigs(sigs); + if (deleted != sigs.size()) { + FFI_LOG(ffi, "Invalid deleted sigs count: %zu instead of %zu.", deleted, sigs.size()); + } + /* delete from the secret key if any */ + if (sec && (sec != &pub)) { + sec->del_sigs(sigs); + } +} + +rnp_result_t +rnp_key_remove_signatures(rnp_key_handle_t handle, + uint32_t flags, + rnp_key_signatures_cb sigcb, + void * app_ctx) +try { + if (!handle) { + return RNP_ERROR_NULL_POINTER; + } + if (!flags && !sigcb) { + return RNP_ERROR_BAD_PARAMETERS; + } + uint32_t origflags = flags; + extract_flag(flags, + RNP_KEY_SIGNATURE_INVALID | RNP_KEY_SIGNATURE_NON_SELF_SIG | + RNP_KEY_SIGNATURE_UNKNOWN_KEY); + if (flags) { + FFI_LOG(handle->ffi, "Invalid flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + flags = origflags; + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* process key itself */ + pgp_key_t *sec = get_key_require_secret(handle); + remove_key_signatures(handle->ffi, *key, sec, flags, sigcb, app_ctx); + + /* process subkeys */ + for (size_t idx = 0; key->is_primary() && (idx < key->subkey_count()); idx++) { + pgp_key_t *sub = pgp_key_get_subkey(key, handle->ffi->pubring, idx); + if (!sub) { + FFI_LOG(handle->ffi, "Failed to get subkey at idx %zu.", idx); + continue; + } + pgp_key_t *subsec = rnp_key_store_get_key_by_fpr(handle->ffi->secring, sub->fp()); + remove_key_signatures(handle->ffi, *sub, subsec, flags, sigcb, app_ctx); + } + /* revalidate key/subkey */ + key->revalidate(*handle->ffi->pubring); + if (sec) { + sec->revalidate(*handle->ffi->secring); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +pk_alg_allows_custom_curve(pgp_pubkey_alg_t pkalg) +{ + switch (pkalg) { + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + return true; + default: + return false; + } +} + +static bool +parse_preferences(json_object *jso, pgp_user_prefs_t &prefs) +{ + static const struct { + const char * key; + enum json_type type; + } properties[] = {{"hashes", json_type_array}, + {"ciphers", json_type_array}, + {"compression", json_type_array}, + {"key server", json_type_string}}; + + for (size_t iprop = 0; iprop < ARRAY_SIZE(properties); iprop++) { + json_object *value = NULL; + const char * key = properties[iprop].key; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + + if (!json_object_is_type(value, properties[iprop].type)) { + return false; + } + try { + if (rnp::str_case_eq(key, "hashes")) { + int length = json_object_array_length(value); + for (int i = 0; i < length; i++) { + json_object *item = json_object_array_get_idx(value, i); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(json_object_get_string(item), &hash_alg)) { + return false; + } + prefs.add_hash_alg(hash_alg); + } + } else if (rnp::str_case_eq(key, "ciphers")) { + int length = json_object_array_length(value); + for (int i = 0; i < length; i++) { + json_object *item = json_object_array_get_idx(value, i); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN; + if (!str_to_cipher(json_object_get_string(item), &symm_alg)) { + return false; + } + prefs.add_symm_alg(symm_alg); + } + } else if (rnp::str_case_eq(key, "compression")) { + int length = json_object_array_length(value); + for (int i = 0; i < length; i++) { + json_object *item = json_object_array_get_idx(value, i); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + pgp_compression_type_t z_alg = PGP_C_UNKNOWN; + if (!str_to_compression_alg(json_object_get_string(item), &z_alg)) { + return false; + } + prefs.add_z_alg(z_alg); + } + } else if (rnp::str_case_eq(key, "key server")) { + prefs.key_server = json_object_get_string(value); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return true; +} + +static bool +parse_keygen_crypto(json_object *jso, rnp_keygen_crypto_params_t &crypto) +{ + static const struct { + const char * key; + enum json_type type; + } properties[] = {{"type", json_type_string}, + {"curve", json_type_string}, + {"length", json_type_int}, + {"hash", json_type_string}}; + + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i].key; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + + if (!json_object_is_type(value, properties[i].type)) { + return false; + } + // TODO: make sure there are no duplicate keys in the JSON + if (rnp::str_case_eq(key, "type")) { + if (!str_to_pubkey_alg(json_object_get_string(value), &crypto.key_alg)) { + return false; + } + } else if (rnp::str_case_eq(key, "length")) { + int length = json_object_get_int(value); + switch (crypto.key_alg) { + case PGP_PKA_RSA: + crypto.rsa.modulus_bit_len = length; + break; + case PGP_PKA_DSA: + crypto.dsa.p_bitlen = length; + break; + case PGP_PKA_ELGAMAL: + crypto.elgamal.key_bitlen = length; + break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "curve")) { + if (!pk_alg_allows_custom_curve(crypto.key_alg)) { + return false; + } + if (!curve_str_to_type(json_object_get_string(value), &crypto.ecc.curve)) { + return false; + } + } else if (rnp::str_case_eq(key, "hash")) { + if (!str_to_hash_alg(json_object_get_string(value), &crypto.hash_alg)) { + return false; + } + } else { + // shouldn't happen + return false; + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return true; +} + +static bool +parse_protection(json_object *jso, rnp_key_protection_params_t &protection) +{ + static const struct { + const char * key; + enum json_type type; + } properties[] = {{"cipher", json_type_string}, + {"mode", json_type_string}, + {"iterations", json_type_int}, + {"hash", json_type_string}}; + + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i].key; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + + if (!json_object_is_type(value, properties[i].type)) { + return false; + } + // TODO: make sure there are no duplicate keys in the JSON + if (rnp::str_case_eq(key, "cipher")) { + if (!str_to_cipher(json_object_get_string(value), &protection.symm_alg)) { + return false; + } + } else if (rnp::str_case_eq(key, "mode")) { + if (!str_to_cipher_mode(json_object_get_string(value), &protection.cipher_mode)) { + return false; + } + } else if (rnp::str_case_eq(key, "iterations")) { + protection.iterations = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "hash")) { + if (!str_to_hash_alg(json_object_get_string(value), &protection.hash_alg)) { + return false; + } + } else { + // shouldn't happen + return false; + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return true; +} + +static bool +parse_keygen_primary(json_object * jso, + rnp_keygen_primary_desc_t & desc, + rnp_key_protection_params_t &prot) +{ + static const char *properties[] = { + "userid", "usage", "expiration", "preferences", "protection"}; + auto &cert = desc.cert; + + if (!parse_keygen_crypto(jso, desc.crypto)) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i]; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + if (rnp::str_case_eq(key, "userid")) { + if (!json_object_is_type(value, json_type_string)) { + return false; + } + auto uid = json_object_get_string(value); + if (strlen(uid) > MAX_ID_LENGTH) { + return false; + } + cert.userid = json_object_get_string(value); + } else if (rnp::str_case_eq(key, "usage")) { + switch (json_object_get_type(value)) { + case json_type_array: { + int length = json_object_array_length(value); + for (int j = 0; j < length; j++) { + json_object *item = json_object_array_get_idx(value, j); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + uint8_t flag = 0; + if (!str_to_key_flag(json_object_get_string(item), &flag)) { + return false; + } + // check for duplicate + if (cert.key_flags & flag) { + return false; + } + cert.key_flags |= flag; + } + } break; + case json_type_string: { + if (!str_to_key_flag(json_object_get_string(value), &cert.key_flags)) { + return false; + } + } break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "expiration")) { + if (!json_object_is_type(value, json_type_int)) { + return false; + } + cert.key_expiration = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "preferences")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } + if (!parse_preferences(value, cert.prefs)) { + return false; + } + if (json_object_object_length(value)) { + return false; + } + } else if (rnp::str_case_eq(key, "protection")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } + if (!parse_protection(value, prot)) { + return false; + } + if (json_object_object_length(value)) { + return false; + } + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return !json_object_object_length(jso); +} + +static bool +parse_keygen_sub(json_object * jso, + rnp_keygen_subkey_desc_t & desc, + rnp_key_protection_params_t &prot) +{ + static const char *properties[] = {"usage", "expiration", "protection"}; + auto & binding = desc.binding; + + if (!parse_keygen_crypto(jso, desc.crypto)) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i]; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + if (rnp::str_case_eq(key, "usage")) { + switch (json_object_get_type(value)) { + case json_type_array: { + int length = json_object_array_length(value); + for (int j = 0; j < length; j++) { + json_object *item = json_object_array_get_idx(value, j); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + uint8_t flag = 0; + if (!str_to_key_flag(json_object_get_string(item), &flag)) { + return false; + } + if (binding.key_flags & flag) { + return false; + } + binding.key_flags |= flag; + } + } break; + case json_type_string: { + if (!str_to_key_flag(json_object_get_string(value), &binding.key_flags)) { + return false; + } + } break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "expiration")) { + if (!json_object_is_type(value, json_type_int)) { + return false; + } + binding.key_expiration = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "protection")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } + if (!parse_protection(value, prot)) { + return false; + } + if (json_object_object_length(value)) { + return false; + } + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return !json_object_object_length(jso); +} + +static bool +gen_json_grips(char **result, const pgp_key_t *primary, const pgp_key_t *sub) +{ + if (!result) { + return true; + } + + json_object *jso = json_object_new_object(); + if (!jso) { + return false; + } + rnp::JSONObject jsowrap(jso); + + char grip[PGP_KEY_GRIP_SIZE * 2 + 1]; + if (primary) { + json_object *jsoprimary = json_object_new_object(); + if (!jsoprimary) { + return false; + } + json_object_object_add(jso, "primary", jsoprimary); + if (!rnp::hex_encode( + primary->grip().data(), primary->grip().size(), grip, sizeof(grip))) { + return false; + } + json_object *jsogrip = json_object_new_string(grip); + if (!jsogrip) { + return false; + } + json_object_object_add(jsoprimary, "grip", jsogrip); + } + if (sub) { + json_object *jsosub = json_object_new_object(); + if (!jsosub) { + return false; + } + json_object_object_add(jso, "sub", jsosub); + if (!rnp::hex_encode(sub->grip().data(), sub->grip().size(), grip, sizeof(grip))) { + return false; + } + json_object *jsogrip = json_object_new_string(grip); + if (!jsogrip) { + return false; + } + json_object_object_add(jsosub, "grip", jsogrip); + } + *result = strdup(json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY)); + return *result; +} + +static rnp_result_t +gen_json_primary_key(rnp_ffi_t ffi, + json_object * jsoparams, + rnp_key_protection_params_t &prot, + pgp_fingerprint_t & fp, + bool protect) +{ + rnp_keygen_primary_desc_t desc = {}; + // desc.crypto is a union + // so at least Clang 12 on Windows zero-initializes the first union member only + // keeping the "larger" member partially unintialized + desc.crypto.dsa.q_bitlen = 0; + + desc.cert.key_expiration = DEFAULT_KEY_EXPIRATION; + if (!parse_keygen_primary(jsoparams, desc, prot)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t pub; + pgp_key_t sec; + desc.crypto.ctx = &ffi->context; + if (!pgp_generate_primary_key(desc, true, sec, pub, ffi->secring->format)) { + return RNP_ERROR_GENERIC; + } + if (!rnp_key_store_add_key(ffi->pubring, &pub)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + /* encrypt secret key if specified */ + if (protect && prot.symm_alg && !sec.protect(prot, ffi->pass_provider, ffi->context)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_add_key(ffi->secring, &sec)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + fp = pub.fp(); + return RNP_SUCCESS; +} + +static rnp_result_t +gen_json_subkey(rnp_ffi_t ffi, + json_object * jsoparams, + pgp_key_t & prim_pub, + pgp_key_t & prim_sec, + pgp_fingerprint_t &fp) +{ + rnp_keygen_subkey_desc_t desc = {}; + rnp_key_protection_params_t prot = {}; + + desc.binding.key_expiration = DEFAULT_KEY_EXPIRATION; + if (!parse_keygen_sub(jsoparams, desc, prot)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!desc.binding.key_flags) { + /* Generate encrypt-only subkeys by default */ + desc.binding.key_flags = PGP_KF_ENCRYPT; + } + pgp_key_t pub; + pgp_key_t sec; + desc.crypto.ctx = &ffi->context; + if (!pgp_generate_subkey(desc, + true, + prim_sec, + prim_pub, + sec, + pub, + ffi->pass_provider, + ffi->secring->format)) { + return RNP_ERROR_GENERIC; + } + if (!rnp_key_store_add_key(ffi->pubring, &pub)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + /* encrypt subkey if specified */ + if (prot.symm_alg && !sec.protect(prot, ffi->pass_provider, ffi->context)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_add_key(ffi->secring, &sec)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + fp = pub.fp(); + return RNP_SUCCESS; +} + +rnp_result_t +rnp_generate_key_json(rnp_ffi_t ffi, const char *json, char **results) +try { + // checks + if (!ffi || !ffi->secring || !json) { + return RNP_ERROR_NULL_POINTER; + } + + // parse the JSON + json_tokener_error error; + json_object * jso = json_tokener_parse_verbose(json, &error); + if (!jso) { + // syntax error or some other issue + FFI_LOG(ffi, "Invalid JSON: %s", json_tokener_error_desc(error)); + return RNP_ERROR_BAD_FORMAT; + } + rnp::JSONObject jsowrap(jso); + + // locate the appropriate sections + rnp_result_t ret = RNP_ERROR_GENERIC; + json_object *jsoprimary = NULL; + json_object *jsosub = NULL; + { + json_object_object_foreach(jso, key, value) + { + json_object **dest = NULL; + + if (rnp::str_case_eq(key, "primary")) { + dest = &jsoprimary; + } else if (rnp::str_case_eq(key, "sub")) { + dest = &jsosub; + } else { + // unrecognized key in the object + FFI_LOG(ffi, "Unexpected key in JSON: %s", key); + return RNP_ERROR_BAD_PARAMETERS; + } + + // duplicate "primary"/"sub" + if (*dest) { + return RNP_ERROR_BAD_PARAMETERS; + } + *dest = value; + } + } + + if (!jsoprimary && !jsosub) { + return RNP_ERROR_BAD_PARAMETERS; + } + + // generate primary key + pgp_key_t * prim_pub = NULL; + pgp_key_t * prim_sec = NULL; + rnp_key_protection_params_t prim_prot = {}; + pgp_fingerprint_t fp; + if (jsoprimary) { + ret = gen_json_primary_key(ffi, jsoprimary, prim_prot, fp, !jsosub); + if (ret) { + return ret; + } + prim_pub = rnp_key_store_get_key_by_fpr(ffi->pubring, fp); + if (!jsosub) { + if (!gen_json_grips(results, prim_pub, NULL)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; + } + prim_sec = rnp_key_store_get_key_by_fpr(ffi->secring, fp); + } else { + /* generate subkey only - find primary key via JSON params */ + json_object *jsoparent = NULL; + if (!json_object_object_get_ex(jsosub, "primary", &jsoparent) || + json_object_object_length(jsoparent) != 1) { + return RNP_ERROR_BAD_PARAMETERS; + } + const char *identifier_type = NULL; + const char *identifier = NULL; + json_object_object_foreach(jsoparent, key, value) + { + if (!json_object_is_type(value, json_type_string)) { + return RNP_ERROR_BAD_PARAMETERS; + } + identifier_type = key; + identifier = json_object_get_string(value); + } + if (!identifier_type || !identifier) { + return RNP_ERROR_BAD_STATE; + } + + pgp_key_search_t locator; + rnp_result_t tmpret = str_to_locator(ffi, &locator, identifier_type, identifier); + if (tmpret) { + return tmpret; + } + + prim_pub = rnp_key_store_search(ffi->pubring, &locator, NULL); + prim_sec = rnp_key_store_search(ffi->secring, &locator, NULL); + if (!prim_sec || !prim_pub) { + return RNP_ERROR_KEY_NOT_FOUND; + } + json_object_object_del(jsosub, "primary"); + } + + /* Generate subkey */ + ret = gen_json_subkey(ffi, jsosub, *prim_pub, *prim_sec, fp); + if (ret) { + if (jsoprimary) { + /* do not leave generated primary key in keyring */ + rnp_key_store_remove_key(ffi->pubring, prim_pub, false); + rnp_key_store_remove_key(ffi->secring, prim_sec, false); + } + return ret; + } + /* Protect the primary key now */ + if (prim_prot.symm_alg && + !prim_sec->protect(prim_prot, ffi->pass_provider, ffi->context)) { + rnp_key_store_remove_key(ffi->pubring, prim_pub, true); + rnp_key_store_remove_key(ffi->secring, prim_sec, true); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t *sub_pub = rnp_key_store_get_key_by_fpr(ffi->pubring, fp); + bool res = gen_json_grips(results, jsoprimary ? prim_pub : NULL, sub_pub); + return res ? RNP_SUCCESS : RNP_ERROR_OUT_OF_MEMORY; +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_ex(rnp_ffi_t ffi, + const char * key_alg, + const char * sub_alg, + uint32_t key_bits, + uint32_t sub_bits, + const char * key_curve, + const char * sub_curve, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + rnp_op_generate_t op = NULL; + rnp_op_generate_t subop = NULL; + rnp_key_handle_t primary = NULL; + rnp_key_handle_t subkey = NULL; + rnp_result_t ret = RNP_ERROR_KEY_GENERATION; + + /* generate primary key */ + if ((ret = rnp_op_generate_create(&op, ffi, key_alg))) { + return ret; + } + if (key_bits && (ret = rnp_op_generate_set_bits(op, key_bits))) { + goto done; + } + if (key_curve && (ret = rnp_op_generate_set_curve(op, key_curve))) { + goto done; + } + if ((ret = rnp_op_generate_set_userid(op, userid))) { + goto done; + } + if ((ret = rnp_op_generate_add_usage(op, "sign"))) { + goto done; + } + if ((ret = rnp_op_generate_add_usage(op, "certify"))) { + goto done; + } + if ((ret = rnp_op_generate_execute(op))) { + goto done; + } + if ((ret = rnp_op_generate_get_key(op, &primary))) { + goto done; + } + /* generate subkey if requested */ + if (!sub_alg) { + goto done; + } + if ((ret = rnp_op_generate_subkey_create(&subop, ffi, primary, sub_alg))) { + goto done; + } + if (sub_bits && (ret = rnp_op_generate_set_bits(subop, sub_bits))) { + goto done; + } + if (sub_curve && (ret = rnp_op_generate_set_curve(subop, sub_curve))) { + goto done; + } + if (password && (ret = rnp_op_generate_set_protection_password(subop, password))) { + goto done; + } + if ((ret = rnp_op_generate_add_usage(subop, "encrypt"))) { + goto done; + } + if ((ret = rnp_op_generate_execute(subop))) { + goto done; + } + if ((ret = rnp_op_generate_get_key(subop, &subkey))) { + goto done; + } +done: + /* only now will protect the primary key - to not spend time on unlocking to sign + * subkey */ + if (!ret && password) { + ret = rnp_key_protect(primary, password, NULL, NULL, NULL, 0); + } + if (ret && primary) { + rnp_key_remove(primary, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET); + } + if (ret && subkey) { + rnp_key_remove(subkey, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET); + } + if (!ret && key) { + *key = primary; + } else { + rnp_key_handle_destroy(primary); + } + rnp_key_handle_destroy(subkey); + rnp_op_generate_destroy(op); + rnp_op_generate_destroy(subop); + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_rsa(rnp_ffi_t ffi, + uint32_t bits, + uint32_t subbits, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex(ffi, + RNP_ALGNAME_RSA, + subbits ? RNP_ALGNAME_RSA : NULL, + bits, + subbits, + NULL, + NULL, + userid, + password, + key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_dsa_eg(rnp_ffi_t ffi, + uint32_t bits, + uint32_t subbits, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex(ffi, + RNP_ALGNAME_DSA, + subbits ? RNP_ALGNAME_ELGAMAL : NULL, + bits, + subbits, + NULL, + NULL, + userid, + password, + key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_ec(rnp_ffi_t ffi, + const char * curve, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex( + ffi, RNP_ALGNAME_ECDSA, RNP_ALGNAME_ECDH, 0, 0, curve, curve, userid, password, key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_25519(rnp_ffi_t ffi, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex(ffi, + RNP_ALGNAME_EDDSA, + RNP_ALGNAME_ECDH, + 0, + 0, + NULL, + "Curve25519", + userid, + password, + key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_sm2(rnp_ffi_t ffi, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex( + ffi, RNP_ALGNAME_SM2, RNP_ALGNAME_SM2, 0, 0, NULL, NULL, userid, password, key); +} +FFI_GUARD + +static pgp_key_flags_t +default_key_flags(pgp_pubkey_alg_t alg, bool subkey) +{ + switch (alg) { + case PGP_PKA_RSA: + return subkey ? PGP_KF_ENCRYPT : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); + case PGP_PKA_DSA: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + return subkey ? PGP_KF_SIGN : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); + case PGP_PKA_SM2: + return subkey ? PGP_KF_ENCRYPT : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); + case PGP_PKA_ECDH: + case PGP_PKA_ELGAMAL: + return PGP_KF_ENCRYPT; + default: + return PGP_KF_NONE; + } +} + +rnp_result_t +rnp_op_generate_create(rnp_op_generate_t *op, rnp_ffi_t ffi, const char *alg) +try { + pgp_pubkey_alg_t key_alg = PGP_PKA_NOTHING; + + if (!op || !ffi || !alg) { + return RNP_ERROR_NULL_POINTER; + } + + if (!ffi->pubring || !ffi->secring) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!str_to_pubkey_alg(alg, &key_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!(pgp_pk_alg_capabilities(key_alg) & PGP_KF_SIGN)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *op = new rnp_op_generate_st(); + (*op)->ffi = ffi; + (*op)->primary = true; + (*op)->crypto.key_alg = key_alg; + (*op)->crypto.ctx = &ffi->context; + (*op)->cert.key_flags = default_key_flags(key_alg, false); + (*op)->cert.key_expiration = DEFAULT_KEY_EXPIRATION; + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_subkey_create(rnp_op_generate_t *op, + rnp_ffi_t ffi, + rnp_key_handle_t primary, + const char * alg) +try { + if (!op || !ffi || !alg || !primary) { + return RNP_ERROR_NULL_POINTER; + } + + if (!ffi->pubring || !ffi->secring) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* TODO: should we do these checks here or may leave it up till generate call? */ + if (!primary->sec || !primary->sec->usable_for(PGP_OP_ADD_SUBKEY)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_pubkey_alg_t key_alg = PGP_PKA_NOTHING; + if (!str_to_pubkey_alg(alg, &key_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *op = new rnp_op_generate_st(); + (*op)->ffi = ffi; + (*op)->primary = false; + (*op)->crypto.key_alg = key_alg; + (*op)->crypto.ctx = &ffi->context; + (*op)->binding.key_flags = default_key_flags(key_alg, true); + (*op)->binding.key_expiration = DEFAULT_KEY_EXPIRATION; + (*op)->primary_sec = primary->sec; + (*op)->primary_pub = primary->pub; + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_bits(rnp_op_generate_t op, uint32_t bits) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + + switch (op->crypto.key_alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + op->crypto.rsa.modulus_bit_len = bits; + break; + case PGP_PKA_ELGAMAL: + op->crypto.elgamal.key_bitlen = bits; + break; + case PGP_PKA_DSA: + op->crypto.dsa.p_bitlen = bits; + break; + default: + return RNP_ERROR_BAD_PARAMETERS; + } + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_hash(rnp_op_generate_t op, const char *hash) +try { + if (!op || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_hash_alg(hash, &op->crypto.hash_alg)) { + FFI_LOG(op->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_dsa_qbits(rnp_op_generate_t op, uint32_t qbits) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (op->crypto.key_alg != PGP_PKA_DSA) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->crypto.dsa.q_bitlen = qbits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_curve(rnp_op_generate_t op, const char *curve) +try { + if (!op || !curve) { + return RNP_ERROR_NULL_POINTER; + } + if (!pk_alg_allows_custom_curve(op->crypto.key_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!curve_str_to_type(curve, &op->crypto.ecc.curve)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_password(rnp_op_generate_t op, const char *password) +try { + if (!op || !password) { + return RNP_ERROR_NULL_POINTER; + } + op->password.assign(password, password + strlen(password) + 1); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_request_password(rnp_op_generate_t op, bool request) +try { + if (!op || !request) { + return RNP_ERROR_NULL_POINTER; + } + op->request_password = request; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_cipher(rnp_op_generate_t op, const char *cipher) +try { + if (!op || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_cipher(cipher, &op->protection.symm_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_hash(rnp_op_generate_t op, const char *hash) +try { + if (!op || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_hash_alg(hash, &op->protection.hash_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_mode(rnp_op_generate_t op, const char *mode) +try { + if (!op || !mode) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_cipher_mode(mode, &op->protection.cipher_mode)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_iterations(rnp_op_generate_t op, uint32_t iterations) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + op->protection.iterations = iterations; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_usage(rnp_op_generate_t op, const char *usage) +try { + if (!op || !usage) { + return RNP_ERROR_NULL_POINTER; + } + uint8_t flag = 0; + if (!str_to_key_flag(usage, &flag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!(pgp_pk_alg_capabilities(op->crypto.key_alg) & flag)) { + return RNP_ERROR_NOT_SUPPORTED; + } + if (op->primary) { + op->cert.key_flags |= flag; + } else { + op->binding.key_flags |= flag; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_usage(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (op->primary) { + op->cert.key_flags = 0; + } else { + op->binding.key_flags = 0; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_userid(rnp_op_generate_t op, const char *userid) +try { + if (!op || !userid) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (strlen(userid) > MAX_ID_LENGTH) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.userid = userid; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_expiration(rnp_op_generate_t op, uint32_t expiration) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (op->primary) { + op->cert.key_expiration = expiration; + } else { + op->binding.key_expiration = expiration; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_pref_hashes(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.set_hash_algs({}); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_pref_hash(rnp_op_generate_t op, const char *hash) +try { + if (!op || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &hash_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.add_hash_alg(hash_alg); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_pref_compression(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.set_z_algs({}); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_pref_compression(rnp_op_generate_t op, const char *compression) +try { + if (!op || !compression) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_compression_type_t z_alg = PGP_C_UNKNOWN; + if (!str_to_compression_alg(compression, &z_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.add_z_alg(z_alg); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_pref_ciphers(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.set_symm_algs({}); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_pref_cipher(rnp_op_generate_t op, const char *cipher) +try { + if (!op || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN; + if (!str_to_cipher(cipher, &symm_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.add_symm_alg(symm_alg); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_pref_keyserver(rnp_op_generate_t op, const char *keyserver) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.key_server = keyserver ? keyserver : ""; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_execute(rnp_op_generate_t op) +try { + if (!op || !op->ffi) { + return RNP_ERROR_NULL_POINTER; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + pgp_key_t pub; + pgp_key_t sec; + pgp_password_provider_t prov; + + if (op->primary) { + rnp_keygen_primary_desc_t keygen = {}; + keygen.crypto = op->crypto; + keygen.cert = op->cert; + op->cert.prefs = {}; /* generate call will free prefs */ + + if (!pgp_generate_primary_key(keygen, true, sec, pub, op->ffi->secring->format)) { + return RNP_ERROR_KEY_GENERATION; + } + } else { + /* subkey generation */ + rnp_keygen_subkey_desc_t keygen = {}; + keygen.crypto = op->crypto; + keygen.binding = op->binding; + if (!pgp_generate_subkey(keygen, + true, + *op->primary_sec, + *op->primary_pub, + sec, + pub, + op->ffi->pass_provider, + op->ffi->secring->format)) { + return RNP_ERROR_KEY_GENERATION; + } + } + + /* add public key part to the keyring */ + if (!(op->gen_pub = rnp_key_store_add_key(op->ffi->pubring, &pub))) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + /* encrypt secret key if requested */ + if (!op->password.empty()) { + prov = {rnp_password_provider_string, (void *) op->password.data()}; + } else if (op->request_password) { + prov = {rnp_password_cb_bounce, op->ffi}; + } + if (prov.callback && !sec.protect(op->protection, prov, op->ffi->context)) { + FFI_LOG(op->ffi, "failed to encrypt the key"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + + /* add secret key to the keyring */ + if (!(op->gen_sec = rnp_key_store_add_key(op->ffi->secring, &sec))) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + ret = RNP_SUCCESS; +done: + op->password.clear(); + if (ret && op->gen_pub) { + rnp_key_store_remove_key(op->ffi->pubring, op->gen_pub, false); + op->gen_pub = NULL; + } + if (ret && op->gen_sec) { + rnp_key_store_remove_key(op->ffi->secring, op->gen_sec, false); + op->gen_sec = NULL; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_get_key(rnp_op_generate_t op, rnp_key_handle_t *handle) +try { + if (!op || !handle) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->gen_sec || !op->gen_pub) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *handle = (rnp_key_handle_t) malloc(sizeof(**handle)); + if (!*handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*handle)->ffi = op->ffi; + (*handle)->pub = op->gen_pub; + (*handle)->sec = op->gen_sec; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_destroy(rnp_op_generate_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_handle_destroy(rnp_key_handle_t key) +try { + // This does not free key->key which is owned by the keyring + free(key); + return RNP_SUCCESS; +} +FFI_GUARD + +void +rnp_buffer_destroy(void *ptr) +{ + free(ptr); +} + +void +rnp_buffer_clear(void *ptr, size_t size) +{ + if (ptr) { + secure_clear(ptr, size); + } +} + +static pgp_key_t * +get_key_require_public(rnp_key_handle_t handle) +{ + if (!handle->pub && handle->sec) { + pgp_key_request_ctx_t request; + request.secret = false; + + // try fingerprint + request.search.type = PGP_KEY_SEARCH_FINGERPRINT; + request.search.by.fingerprint = handle->sec->fp(); + handle->pub = pgp_request_key(&handle->ffi->key_provider, &request); + if (handle->pub) { + return handle->pub; + } + + // try keyid + request.search.type = PGP_KEY_SEARCH_KEYID; + request.search.by.keyid = handle->sec->keyid(); + handle->pub = pgp_request_key(&handle->ffi->key_provider, &request); + } + return handle->pub; +} + +static pgp_key_t * +get_key_prefer_public(rnp_key_handle_t handle) +{ + pgp_key_t *pub = get_key_require_public(handle); + return pub ? pub : get_key_require_secret(handle); +} + +static pgp_key_t * +get_key_require_secret(rnp_key_handle_t handle) +{ + if (!handle->sec && handle->pub) { + pgp_key_request_ctx_t request; + request.secret = true; + + // try fingerprint + request.search.type = PGP_KEY_SEARCH_FINGERPRINT; + request.search.by.fingerprint = handle->pub->fp(); + handle->sec = pgp_request_key(&handle->ffi->key_provider, &request); + if (handle->sec) { + return handle->sec; + } + + // try keyid + request.search.type = PGP_KEY_SEARCH_KEYID; + request.search.by.keyid = handle->pub->keyid(); + handle->sec = pgp_request_key(&handle->ffi->key_provider, &request); + } + return handle->sec; +} + +static rnp_result_t +key_get_uid_at(pgp_key_t *key, size_t idx, char **uid) +{ + if (!key || !uid) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= key->uid_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + *uid = strdup(key->get_uid(idx).str.c_str()); + if (!*uid) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_add_uid(rnp_key_handle_t handle, + const char * uid, + const char * hash, + uint32_t expiration, + uint8_t key_flags, + bool primary) +try { + if (!handle || !uid) { + return RNP_ERROR_NULL_POINTER; + } + /* setup parameters */ + if (!hash) { + hash = DEFAULT_HASH_ALG; + } + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &hash_alg)) { + FFI_LOG(handle->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (strlen(uid) > MAX_ID_LENGTH) { + FFI_LOG(handle->ffi, "UserID too long"); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_selfsig_cert_info_t info; + info.userid = uid; + info.key_flags = key_flags; + info.key_expiration = expiration; + info.primary = primary; + + /* obtain and unlok secret key */ + pgp_key_t *secret_key = get_key_require_secret(handle); + if (!secret_key || !secret_key->usable_for(PGP_OP_ADD_USERID)) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + pgp_key_t *public_key = get_key_prefer_public(handle); + if (!public_key && secret_key->format == PGP_KEY_STORE_G10) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + rnp::KeyLocker seclock(*secret_key); + if (secret_key->is_locked() && + !secret_key->unlock(handle->ffi->pass_provider, PGP_OP_ADD_USERID)) { + return RNP_ERROR_BAD_PASSWORD; + } + /* add and certify userid */ + secret_key->add_uid_cert(info, hash_alg, handle->ffi->context, public_key); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_primary_uid(rnp_key_handle_t handle, char **uid) +try { + if (!handle || !uid) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (key->has_primary_uid()) { + return key_get_uid_at(key, key->get_primary_uid(), uid); + } + for (size_t i = 0; i < key->uid_count(); i++) { + if (!key->get_uid(i).valid) { + continue; + } + return key_get_uid_at(key, i, uid); + } + return RNP_ERROR_BAD_PARAMETERS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_uid_count(rnp_key_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + + *count = get_key_prefer_public(handle)->uid_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_uid_at(rnp_key_handle_t handle, size_t idx, char **uid) +try { + if (handle == NULL || uid == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_prefer_public(handle); + return key_get_uid_at(key, idx, uid); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_uid_handle_at(rnp_key_handle_t key, size_t idx, rnp_uid_handle_t *uid) +try { + if (!key || !uid) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *akey = get_key_prefer_public(key); + if (!akey) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (idx >= akey->uid_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *uid = (rnp_uid_handle_t) malloc(sizeof(**uid)); + if (!*uid) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + (*uid)->ffi = key->ffi; + (*uid)->key = akey; + (*uid)->idx = idx; + return RNP_SUCCESS; +} +FFI_GUARD + +static pgp_userid_t * +rnp_uid_handle_get_uid(rnp_uid_handle_t uid) +{ + if (!uid || !uid->key) { + return NULL; + } + return &uid->key->get_uid(uid->idx); +} + +rnp_result_t +rnp_uid_get_type(rnp_uid_handle_t uid, uint32_t *type) +try { + if (!type) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + switch (id->pkt.tag) { + case PGP_PKT_USER_ID: + *type = RNP_USER_ID; + return RNP_SUCCESS; + case PGP_PKT_USER_ATTR: + *type = RNP_USER_ATTR; + return RNP_SUCCESS; + default: + return RNP_ERROR_BAD_STATE; + } +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_data(rnp_uid_handle_t uid, void **data, size_t *size) +try { + if (!data || !size) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + *data = malloc(id->pkt.uid_len); + if (id->pkt.uid_len && !*data) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*data, id->pkt.uid, id->pkt.uid_len); + *size = id->pkt.uid_len; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_is_primary(rnp_uid_handle_t uid, bool *primary) +try { + if (!primary) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + *primary = uid->key->has_primary_uid() && (uid->key->get_primary_uid() == uid->idx); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_is_valid(rnp_uid_handle_t uid, bool *valid) +try { + if (!valid) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + *valid = id->valid; + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_key_return_signature(rnp_ffi_t ffi, + pgp_key_t * key, + pgp_subsig_t * subsig, + rnp_signature_handle_t *sig) +{ + *sig = (rnp_signature_handle_t) calloc(1, sizeof(**sig)); + if (!*sig) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*sig)->ffi = ffi; + (*sig)->key = key; + (*sig)->sig = subsig; + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_get_signature_count(rnp_key_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *count = key->keysig_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_signature_at(rnp_key_handle_t handle, size_t idx, rnp_signature_handle_t *sig) +try { + if (!handle || !sig) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key || (idx >= key->keysig_count())) { + return RNP_ERROR_BAD_PARAMETERS; + } + return rnp_key_return_signature(handle->ffi, key, &key->get_keysig(idx), sig); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_revocation_signature(rnp_key_handle_t handle, rnp_signature_handle_t *sig) +try { + if (!handle || !sig) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->revoked()) { + *sig = NULL; + return RNP_SUCCESS; + } + if (!key->has_sig(key->revocation().sigid)) { + return RNP_ERROR_BAD_STATE; + } + return rnp_key_return_signature( + handle->ffi, key, &key->get_sig(key->revocation().sigid), sig); +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_signature_count(rnp_uid_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *count = handle->key->get_uid(handle->idx).sig_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_signature_at(rnp_uid_handle_t handle, size_t idx, rnp_signature_handle_t *sig) +try { + if (!handle || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_userid_t &uid = handle->key->get_uid(handle->idx); + if (idx >= uid.sig_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + const pgp_sig_id_t &sigid = uid.get_sig(idx); + if (!handle->key->has_sig(sigid)) { + return RNP_ERROR_BAD_STATE; + } + return rnp_key_return_signature( + handle->ffi, handle->key, &handle->key->get_sig(sigid), sig); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_type(rnp_signature_handle_t handle, char **type) +try { + if (!handle || !type) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + auto sigtype = id_str_pair::lookup(sig_type_map, handle->sig->sig.type()); + return ret_str_value(sigtype, type); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_alg(rnp_signature_handle_t handle, char **alg) +try { + if (!handle || !alg) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + return get_map_value(pubkey_alg_map, handle->sig->sig.palg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_hash_alg(rnp_signature_handle_t handle, char **alg) +try { + if (!handle || !alg) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + return get_map_value(hash_alg_map, handle->sig->sig.halg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_creation(rnp_signature_handle_t handle, uint32_t *create) +try { + if (!handle || !create) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + *create = handle->sig->sig.creation(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_expiration(rnp_signature_handle_t handle, uint32_t *expires) +try { + if (!handle || !expires) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + *expires = handle->sig->sig.expiration(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_keyid(rnp_signature_handle_t handle, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!handle->sig->sig.has_keyid()) { + *result = NULL; + return RNP_SUCCESS; + } + pgp_key_id_t keyid = handle->sig->sig.keyid(); + return hex_encode_value(keyid.data(), keyid.size(), result); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_key_fprint(rnp_signature_handle_t handle, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!handle->sig->sig.has_keyfp()) { + *result = NULL; + return RNP_SUCCESS; + } + pgp_fingerprint_t keyfp = handle->sig->sig.keyfp(); + return hex_encode_value(keyfp.fingerprint, keyfp.length, result); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_signer(rnp_signature_handle_t sig, rnp_key_handle_t *key) +try { + if (!sig || !sig->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!sig->sig->sig.has_keyid()) { + *key = NULL; + return RNP_SUCCESS; + } + pgp_key_search_t locator(PGP_KEY_SEARCH_KEYID); + locator.by.keyid = sig->sig->sig.keyid(); + return rnp_locate_key_int(sig->ffi, locator, key); +} +FFI_GUARD + +rnp_result_t +rnp_signature_is_valid(rnp_signature_handle_t sig, uint32_t flags) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + if (!sig->sig || sig->own_sig || flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!sig->sig->validity.validated) { + pgp_key_t *signer = + pgp_sig_get_signer(*sig->sig, sig->ffi->pubring, &sig->ffi->key_provider); + if (!signer) { + return RNP_ERROR_KEY_NOT_FOUND; + } + signer->validate_sig(*sig->key, *sig->sig, sig->ffi->context); + } + + if (!sig->sig->validity.validated) { + return RNP_ERROR_VERIFICATION_FAILED; + } + if (sig->sig->validity.expired) { + return RNP_ERROR_SIGNATURE_EXPIRED; + } + return sig->sig->valid() ? RNP_SUCCESS : RNP_ERROR_SIGNATURE_INVALID; +} +FFI_GUARD + +rnp_result_t +rnp_signature_packet_to_json(rnp_signature_handle_t sig, uint32_t flags, char **json) +try { + if (!sig || !json) { + return RNP_ERROR_NULL_POINTER; + } + + rnp::MemoryDest memdst; + sig->sig->sig.write(memdst.dst()); + auto vec = memdst.to_vector(); + rnp::MemorySource memsrc(vec); + return rnp_dump_src_to_json(&memsrc.src(), flags, json); +} +FFI_GUARD + +rnp_result_t +rnp_signature_remove(rnp_key_handle_t key, rnp_signature_handle_t sig) +try { + if (!key || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (sig->own_sig || !sig->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *pkey = get_key_require_public(key); + pgp_key_t *skey = get_key_require_secret(key); + if (!pkey && !skey) { + return RNP_ERROR_BAD_PARAMETERS; + } + const pgp_sig_id_t sigid = sig->sig->sigid; + bool ok = false; + if (pkey) { + ok = pkey->del_sig(sigid); + pkey->revalidate(*key->ffi->pubring); + } + if (skey) { + /* secret key may not have signature, but we still need to delete it at least once to + * succeed */ + ok = skey->del_sig(sigid) || ok; + skey->revalidate(*key->ffi->secring); + } + return ok ? RNP_SUCCESS : RNP_ERROR_NO_SIGNATURES_FOUND; +} +FFI_GUARD + +static rnp_result_t +write_signature(rnp_signature_handle_t sig, pgp_dest_t &dst) +{ + sig->sig->rawpkt.write(dst); + dst_flush(&dst); + return dst.werr; +} + +rnp_result_t +rnp_signature_export(rnp_signature_handle_t sig, rnp_output_t output, uint32_t flags) +try { + if (!sig || !sig->sig || !output) { + return RNP_ERROR_NULL_POINTER; + } + bool need_armor = extract_flag(flags, RNP_KEY_EXPORT_ARMORED); + if (flags) { + FFI_LOG(sig->ffi, "Invalid flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_result_t ret; + if (need_armor) { + rnp::ArmoredDest armor(output->dst, PGP_ARMORED_PUBLIC_KEY); + ret = write_signature(sig, armor.dst()); + } else { + ret = write_signature(sig, output->dst); + } + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_signature_handle_destroy(rnp_signature_handle_t sig) +try { + if (sig && sig->own_sig) { + delete sig->sig; + } + free(sig); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_is_revoked(rnp_uid_handle_t uid, bool *result) +try { + if (!uid || !result) { + return RNP_ERROR_NULL_POINTER; + } + + if (!uid->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *result = uid->key->get_uid(uid->idx).revoked; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_revocation_signature(rnp_uid_handle_t uid, rnp_signature_handle_t *sig) +try { + if (!uid || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (!uid->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (uid->idx >= uid->key->uid_count()) { + return RNP_ERROR_BAD_STATE; + } + const pgp_userid_t &userid = uid->key->get_uid(uid->idx); + if (!userid.revoked) { + *sig = NULL; + return RNP_SUCCESS; + } + if (!uid->key->has_sig(userid.revocation.sigid)) { + return RNP_ERROR_BAD_STATE; + } + return rnp_key_return_signature( + uid->ffi, uid->key, &uid->key->get_sig(userid.revocation.sigid), sig); +} +FFI_GUARD + +rnp_result_t +rnp_uid_remove(rnp_key_handle_t key, rnp_uid_handle_t uid) +try { + if (!key || !uid) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *pkey = get_key_require_public(key); + pgp_key_t *skey = get_key_require_secret(key); + if (!pkey && !skey) { + return RNP_ERROR_BAD_PARAMETERS; + } + if ((uid->key != pkey) && (uid->key != skey)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + bool ok = false; + if (pkey && (pkey->uid_count() > uid->idx)) { + pkey->del_uid(uid->idx); + pkey->revalidate(*key->ffi->pubring); + ok = true; + } + if (skey && (skey->uid_count() > uid->idx)) { + skey->del_uid(uid->idx); + skey->revalidate(*key->ffi->secring); + ok = true; + } + return ok ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_handle_destroy(rnp_uid_handle_t uid) +try { + free(uid); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_subkey_count(rnp_key_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + *count = key->subkey_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_subkey_at(rnp_key_handle_t handle, size_t idx, rnp_key_handle_t *subkey) +try { + if (!handle || !subkey) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (idx >= key->subkey_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_search_t locator(PGP_KEY_SEARCH_FINGERPRINT); + locator.by.fingerprint = key->get_subkey_fp(idx); + return rnp_locate_key_int(handle->ffi, locator, subkey); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_default_key(rnp_key_handle_t primary_key, + const char * usage, + uint32_t flags, + rnp_key_handle_t *default_key) +try { + if (!primary_key || !usage || !default_key) { + return RNP_ERROR_NULL_POINTER; + } + uint8_t keyflag = 0; + if (!str_to_key_flag(usage, &keyflag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + bool no_primary = extract_flag(flags, RNP_KEY_SUBKEYS_ONLY); + if (flags) { + FFI_LOG(primary_key->ffi, "Invalid flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_op_t op = PGP_OP_UNKNOWN; + bool secret = false; + switch (keyflag) { + case PGP_KF_SIGN: + op = PGP_OP_SIGN; + secret = true; + break; + case PGP_KF_CERTIFY: + op = PGP_OP_CERTIFY; + secret = true; + break; + case PGP_KF_ENCRYPT: + op = PGP_OP_ENCRYPT; + break; + default: + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *key = get_key_prefer_public(primary_key); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *defkey = + find_suitable_key(op, key, &primary_key->ffi->key_provider, no_primary); + if (!defkey) { + *default_key = NULL; + return RNP_ERROR_NO_SUITABLE_KEY; + } + + pgp_key_search_t search(PGP_KEY_SEARCH_FINGERPRINT); + search.by.fingerprint = defkey->fp(); + + rnp_result_t ret = rnp_locate_key_int(primary_key->ffi, search, default_key, secret); + + if (!*default_key && !ret) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_alg(rnp_key_handle_t handle, char **alg) +try { + if (!handle || !alg) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + return get_map_value(pubkey_alg_map, key->alg(), alg); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_bits(rnp_key_handle_t handle, uint32_t *bits) +try { + if (!handle || !bits) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + size_t _bits = key->material().bits(); + if (!_bits) { + return RNP_ERROR_BAD_PARAMETERS; + } + *bits = _bits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_dsa_qbits(rnp_key_handle_t handle, uint32_t *qbits) +try { + if (!handle || !qbits) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + size_t _qbits = key->material().qbits(); + if (!_qbits) { + return RNP_ERROR_BAD_PARAMETERS; + } + *qbits = _qbits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_curve(rnp_key_handle_t handle, char **curve) +try { + if (!handle || !curve) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t * key = get_key_prefer_public(handle); + pgp_curve_t _curve = key->curve(); + if (_curve == PGP_CURVE_UNKNOWN) { + return RNP_ERROR_BAD_PARAMETERS; + } + const char *curvename = NULL; + if (!curve_type_to_str(_curve, &curvename)) { + return RNP_ERROR_BAD_PARAMETERS; + } + char *curvenamecp = strdup(curvename); + if (!curvenamecp) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *curve = curvenamecp; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_fprint(rnp_key_handle_t handle, char **fprint) +try { + if (!handle || !fprint) { + return RNP_ERROR_NULL_POINTER; + } + + const pgp_fingerprint_t &fp = get_key_prefer_public(handle)->fp(); + return hex_encode_value(fp.fingerprint, fp.length, fprint); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_keyid(rnp_key_handle_t handle, char **keyid) +try { + if (!handle || !keyid) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + return hex_encode_value(key->keyid().data(), key->keyid().size(), keyid); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_grip(rnp_key_handle_t handle, char **grip) +try { + if (!handle || !grip) { + return RNP_ERROR_NULL_POINTER; + } + + const pgp_key_grip_t &kgrip = get_key_prefer_public(handle)->grip(); + return hex_encode_value(kgrip.data(), kgrip.size(), grip); +} +FFI_GUARD + +static const pgp_key_grip_t * +rnp_get_grip_by_fp(rnp_ffi_t ffi, const pgp_fingerprint_t &fp) +{ + pgp_key_t *key = NULL; + if (ffi->pubring) { + key = rnp_key_store_get_key_by_fpr(ffi->pubring, fp); + } + if (!key && ffi->secring) { + key = rnp_key_store_get_key_by_fpr(ffi->secring, fp); + } + return key ? &key->grip() : NULL; +} + +rnp_result_t +rnp_key_get_primary_grip(rnp_key_handle_t handle, char **grip) +try { + if (!handle || !grip) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key->is_subkey()) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->has_primary_fp()) { + *grip = NULL; + return RNP_SUCCESS; + } + const pgp_key_grip_t *pgrip = rnp_get_grip_by_fp(handle->ffi, key->primary_fp()); + if (!pgrip) { + *grip = NULL; + return RNP_SUCCESS; + } + return hex_encode_value(pgrip->data(), pgrip->size(), grip); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_primary_fprint(rnp_key_handle_t handle, char **fprint) +try { + if (!handle || !fprint) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key->is_subkey()) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->has_primary_fp()) { + *fprint = NULL; + return RNP_SUCCESS; + } + const pgp_fingerprint_t &fp = key->primary_fp(); + return hex_encode_value(fp.fingerprint, fp.length, fprint); +} +FFI_GUARD + +rnp_result_t +rnp_key_allows_usage(rnp_key_handle_t handle, const char *usage, bool *result) +try { + if (!handle || !usage || !result) { + return RNP_ERROR_NULL_POINTER; + } + uint8_t flag = 0; + if (!str_to_key_flag(usage, &flag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->flags() & flag; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_creation(rnp_key_handle_t handle, uint32_t *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->creation(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_revoked(rnp_key_handle_t handle, bool *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->revoked(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_valid(rnp_key_handle_t handle, bool *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_require_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->validated()) { + key->validate(*handle->ffi->pubring); + } + if (!key->validated()) { + return RNP_ERROR_VERIFICATION_FAILED; + } + *result = key->valid(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_valid_till(rnp_key_handle_t handle, uint32_t *result) +try { + if (!result) { + return RNP_ERROR_NULL_POINTER; + } + uint64_t res = 0; + rnp_result_t ret = rnp_key_valid_till64(handle, &res); + if (ret) { + return ret; + } + if (res == UINT64_MAX) { + *result = UINT32_MAX; + } else if (res >= UINT32_MAX) { + *result = UINT32_MAX - 1; + } else { + *result = (uint32_t) res; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_valid_till64(rnp_key_handle_t handle, uint64_t *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_require_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!key->validated()) { + key->validate(*handle->ffi->pubring); + } + if (!key->validated()) { + return RNP_ERROR_VERIFICATION_FAILED; + } + + if (key->is_subkey()) { + /* check validity time of the primary key as well */ + pgp_key_t *primary = rnp_key_store_get_primary_key(handle->ffi->pubring, key); + if (!primary) { + /* no primary key - subkey considered as never valid */ + *result = 0; + return RNP_SUCCESS; + } + if (!primary->validated()) { + primary->validate(*handle->ffi->pubring); + } + if (!primary->validated()) { + return RNP_ERROR_VERIFICATION_FAILED; + } + *result = key->valid_till(); + } else { + *result = key->valid_till(); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_expiration(rnp_key_handle_t handle, uint32_t *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->expiration(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_set_expiration(rnp_key_handle_t key, uint32_t expiry) +try { + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *pkey = get_key_prefer_public(key); + if (!pkey) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *skey = get_key_require_secret(key); + if (!skey) { + FFI_LOG(key->ffi, "Secret key required."); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (pkey->is_primary()) { + if (!pgp_key_set_expiration( + pkey, skey, expiry, key->ffi->pass_provider, key->ffi->context)) { + return RNP_ERROR_GENERIC; + } + pkey->revalidate(*key->ffi->pubring); + if (pkey != skey) { + skey->revalidate(*key->ffi->secring); + } + return RNP_SUCCESS; + } + + /* for subkey we need primary key */ + if (!pkey->has_primary_fp()) { + FFI_LOG(key->ffi, "Primary key fp not available."); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_search_t search(PGP_KEY_SEARCH_FINGERPRINT); + search.by.fingerprint = pkey->primary_fp(); + pgp_key_t *prim_sec = find_key(key->ffi, search, true, true); + if (!prim_sec) { + FFI_LOG(key->ffi, "Primary secret key not found."); + return RNP_ERROR_KEY_NOT_FOUND; + } + if (!pgp_subkey_set_expiration( + pkey, prim_sec, skey, expiry, key->ffi->pass_provider, key->ffi->context)) { + return RNP_ERROR_GENERIC; + } + prim_sec->revalidate(*key->ffi->secring); + pgp_key_t *prim_pub = find_key(key->ffi, search, false, true); + if (prim_pub) { + prim_pub->revalidate(*key->ffi->pubring); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_revocation_reason(rnp_key_handle_t handle, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key || !key->revoked()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *result = strdup(key->revocation().reason.c_str()); + if (!*result) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_key_is_revoked_with_code(rnp_key_handle_t handle, bool *result, int code) +{ + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key || !key->revoked()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *result = key->revocation().code == code; + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_is_superseded(rnp_key_handle_t handle, bool *result) +try { + return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_SUPERSEDED); +} +FFI_GUARD + +rnp_result_t +rnp_key_is_compromised(rnp_key_handle_t handle, bool *result) +try { + return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_COMPROMISED); +} +FFI_GUARD + +rnp_result_t +rnp_key_is_retired(rnp_key_handle_t handle, bool *result) +try { + return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_RETIRED); +} +FFI_GUARD + +rnp_result_t +rnp_key_is_expired(rnp_key_handle_t handle, bool *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->expired(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_type(rnp_key_handle_t key, char **type) +try { + if (!key || !type) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + + const pgp_s2k_t &s2k = key->sec->pkt().sec_protection.s2k; + const char * res = "Unknown"; + if (s2k.usage == PGP_S2KU_NONE) { + res = "None"; + } + if ((s2k.usage == PGP_S2KU_ENCRYPTED) && (s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + res = "Encrypted"; + } + if ((s2k.usage == PGP_S2KU_ENCRYPTED_AND_HASHED) && + (s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + res = "Encrypted-Hashed"; + } + if ((s2k.specifier == PGP_S2KS_EXPERIMENTAL) && + (s2k.gpg_ext_num == PGP_S2K_GPG_NO_SECRET)) { + res = "GPG-None"; + } + if ((s2k.specifier == PGP_S2KS_EXPERIMENTAL) && + (s2k.gpg_ext_num == PGP_S2K_GPG_SMARTCARD)) { + res = "GPG-Smartcard"; + } + + return ret_str_value(res, type); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_mode(rnp_key_handle_t key, char **mode) +try { + if (!key || !mode) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (key->sec->pkt().sec_protection.s2k.usage == PGP_S2KU_NONE) { + return ret_str_value("None", mode); + } + if (key->sec->pkt().sec_protection.s2k.specifier == PGP_S2KS_EXPERIMENTAL) { + return ret_str_value("Unknown", mode); + } + + return get_map_value(cipher_mode_map, key->sec->pkt().sec_protection.cipher_mode, mode); +} +FFI_GUARD + +static bool +pgp_key_has_encryption_info(const pgp_key_t *key) +{ + return (key->pkt().sec_protection.s2k.usage != PGP_S2KU_NONE) && + (key->pkt().sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL); +} + +rnp_result_t +rnp_key_get_protection_cipher(rnp_key_handle_t key, char **cipher) +try { + if (!key || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pgp_key_has_encryption_info(key->sec)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + return get_map_value(symm_alg_map, key->sec->pkt().sec_protection.symm_alg, cipher); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_hash(rnp_key_handle_t key, char **hash) +try { + if (!key || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pgp_key_has_encryption_info(key->sec)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + return get_map_value(hash_alg_map, key->sec->pkt().sec_protection.s2k.hash_alg, hash); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_iterations(rnp_key_handle_t key, size_t *iterations) +try { + if (!key || !iterations) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pgp_key_has_encryption_info(key->sec)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (key->sec->pkt().sec_protection.s2k.specifier == PGP_S2KS_ITERATED_AND_SALTED) { + *iterations = pgp_s2k_decode_iterations(key->sec->pkt().sec_protection.s2k.iterations); + } else { + *iterations = 1; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_locked(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_locked(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_lock(rnp_key_handle_t handle) +try { + if (handle == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + if (!key->lock()) { + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_unlock(rnp_key_handle_t handle, const char *password) +try { + if (!handle) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + bool ok = false; + if (password) { + pgp_password_provider_t prov(rnp_password_provider_string, + reinterpret_cast<void *>(const_cast<char *>(password))); + ok = key->unlock(prov); + } else { + ok = key->unlock(handle->ffi->pass_provider); + } + if (!ok) { + // likely a bad password + return RNP_ERROR_BAD_PASSWORD; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_protected(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_protected(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_protect(rnp_key_handle_t handle, + const char * password, + const char * cipher, + const char * cipher_mode, + const char * hash, + size_t iterations) +try { + rnp_key_protection_params_t protection = {}; + + // checks + if (!handle || !password) { + return RNP_ERROR_NULL_POINTER; + } + + if (cipher && !str_to_cipher(cipher, &protection.symm_alg)) { + FFI_LOG(handle->ffi, "Invalid cipher: %s", cipher); + return RNP_ERROR_BAD_PARAMETERS; + } + if (cipher_mode && !str_to_cipher_mode(cipher_mode, &protection.cipher_mode)) { + FFI_LOG(handle->ffi, "Invalid cipher mode: %s", cipher_mode); + return RNP_ERROR_BAD_PARAMETERS; + } + if (hash && !str_to_hash_alg(hash, &protection.hash_alg)) { + FFI_LOG(handle->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + protection.iterations = iterations; + + // get the key + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + pgp_key_pkt_t * decrypted_key = NULL; + const std::string pass = password; + if (key->encrypted()) { + pgp_password_ctx_t ctx(PGP_OP_PROTECT, key); + decrypted_key = pgp_decrypt_seckey(*key, handle->ffi->pass_provider, ctx); + if (!decrypted_key) { + return RNP_ERROR_GENERIC; + } + } + bool res = key->protect( + decrypted_key ? *decrypted_key : key->pkt(), protection, pass, handle->ffi->context); + delete decrypted_key; + return res ? RNP_SUCCESS : RNP_ERROR_GENERIC; +} +FFI_GUARD + +rnp_result_t +rnp_key_unprotect(rnp_key_handle_t handle, const char *password) +try { + // checks + if (!handle) { + return RNP_ERROR_NULL_POINTER; + } + + // get the key + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + bool ok = false; + if (password) { + pgp_password_provider_t prov(rnp_password_provider_string, + reinterpret_cast<void *>(const_cast<char *>(password))); + ok = key->unprotect(prov, handle->ffi->context); + } else { + ok = key->unprotect(handle->ffi->pass_provider, handle->ffi->context); + } + if (!ok) { + // likely a bad password + return RNP_ERROR_BAD_PASSWORD; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_primary(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_prefer_public(handle); + if (key->format == PGP_KEY_STORE_G10) { + // we can't currently determine this for a G10 secret key + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_primary(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_sub(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_prefer_public(handle); + if (key->format == PGP_KEY_STORE_G10) { + // we can't currently determine this for a G10 secret key + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_subkey(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_have_secret(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + *result = handle->sec != NULL; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_have_public(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + *result = handle->pub != NULL; + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +key_to_bytes(pgp_key_t *key, uint8_t **buf, size_t *buf_len) +{ + auto vec = rnp_key_to_vec(*key); + *buf = (uint8_t *) calloc(1, vec.size()); + if (!*buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*buf, vec.data(), vec.size()); + *buf_len = vec.size(); + return RNP_SUCCESS; +} + +rnp_result_t +rnp_get_public_key_data(rnp_key_handle_t handle, uint8_t **buf, size_t *buf_len) +try { + // checks + if (!handle || !buf || !buf_len) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = handle->pub; + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + return key_to_bytes(key, buf, buf_len); +} +FFI_GUARD + +rnp_result_t +rnp_get_secret_key_data(rnp_key_handle_t handle, uint8_t **buf, size_t *buf_len) +try { + // checks + if (!handle || !buf || !buf_len) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = handle->sec; + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + return key_to_bytes(key, buf, buf_len); +} +FFI_GUARD + +static bool +add_json_string_field(json_object *jso, const char *key, const char *value) +{ + json_object *jsostr = json_object_new_string(value); + if (!jsostr) { + return false; + } + json_object_object_add(jso, key, jsostr); + return true; +} + +static bool +add_json_int_field(json_object *jso, const char *key, int32_t value) +{ + json_object *jsoval = json_object_new_int(value); + if (!jsoval) { + return false; + } + json_object_object_add(jso, key, jsoval); + return true; +} + +static bool +add_json_key_usage(json_object *jso, uint8_t key_flags) +{ + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(key_usage_map); i++) { + if (key_usage_map[i].id & key_flags) { + json_object *jsostr = json_object_new_string(key_usage_map[i].str); + if (!jsostr || json_object_array_add(jsoarr, jsostr)) { + json_object_put(jsoarr); + return false; + } + } + } + if (json_object_array_length(jsoarr)) { + json_object_object_add(jso, "usage", jsoarr); + } else { + json_object_put(jsoarr); + } + return true; +} + +static bool +add_json_key_flags(json_object *jso, uint8_t key_flags) +{ + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(key_flags_map); i++) { + if (key_flags_map[i].id & key_flags) { + json_object *jsostr = json_object_new_string(key_flags_map[i].str); + if (!jsostr || json_object_array_add(jsoarr, jsostr)) { + json_object_put(jsoarr); + return false; + } + } + } + if (json_object_array_length(jsoarr)) { + json_object_object_add(jso, "flags", jsoarr); + } else { + json_object_put(jsoarr); + } + return true; +} + +static rnp_result_t +add_json_mpis(json_object *jso, ...) +{ + va_list ap; + const char * name; + rnp_result_t ret = RNP_ERROR_GENERIC; + + va_start(ap, jso); + while ((name = va_arg(ap, const char *))) { + pgp_mpi_t *val = va_arg(ap, pgp_mpi_t *); + if (!val) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + char *hex = mpi2hex(val); + if (!hex) { + // this could probably be other things + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + json_object *jsostr = json_object_new_string(hex); + free(hex); + if (!jsostr) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + json_object_object_add(jso, name, jsostr); + } + ret = RNP_SUCCESS; + +done: + va_end(ap); + return ret; +} + +static rnp_result_t +add_json_public_mpis(json_object *jso, pgp_key_t *key) +{ + const pgp_key_material_t &km = key->material(); + switch (km.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return add_json_mpis(jso, "n", &km.rsa.n, "e", &km.rsa.e, NULL); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return add_json_mpis(jso, "p", &km.eg.p, "g", &km.eg.g, "y", &km.eg.y, NULL); + case PGP_PKA_DSA: + return add_json_mpis( + jso, "p", &km.dsa.p, "q", &km.dsa.q, "g", &km.dsa.g, "y", &km.dsa.y, NULL); + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + return add_json_mpis(jso, "point", &km.ec.p, NULL); + default: + return RNP_ERROR_NOT_SUPPORTED; + } + return RNP_SUCCESS; +} + +static rnp_result_t +add_json_secret_mpis(json_object *jso, pgp_key_t *key) +{ + const pgp_key_material_t &km = key->material(); + switch (key->alg()) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return add_json_mpis( + jso, "d", &km.rsa.d, "p", &km.rsa.p, "q", &km.rsa.q, "u", &km.rsa.u, NULL); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return add_json_mpis(jso, "x", &km.eg.x, NULL); + case PGP_PKA_DSA: + return add_json_mpis(jso, "x", &km.dsa.x, NULL); + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + return add_json_mpis(jso, "x", &km.ec.x, NULL); + default: + return RNP_ERROR_NOT_SUPPORTED; + } + return RNP_SUCCESS; +} + +static rnp_result_t +add_json_sig_mpis(json_object *jso, const pgp_signature_t *sig) +{ + pgp_signature_material_t material = {}; + try { + if (!sig->parse_material(material)) { + return RNP_ERROR_BAD_PARAMETERS; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + switch (sig->palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return add_json_mpis(jso, "sig", &material.rsa.s, NULL); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return add_json_mpis(jso, "r", &material.eg.r, "s", &material.eg.s, NULL); + case PGP_PKA_DSA: + return add_json_mpis(jso, "r", &material.dsa.r, "s", &material.dsa.s, NULL); + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + return add_json_mpis(jso, "r", &material.ecc.r, "s", &material.ecc.s, NULL); + default: + // TODO: we could use info->unknown and add a hex string of raw data here + return RNP_ERROR_NOT_SUPPORTED; + } + return RNP_SUCCESS; +} + +static bool +add_json_user_prefs(json_object *jso, const pgp_user_prefs_t &prefs) +{ + // TODO: instead of using a string "Unknown" as a fallback for these, + // we could add a string of hex/dec (or even an int) + if (!prefs.symm_algs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "ciphers", jsoarr); + for (auto alg : prefs.symm_algs) { + const char * name = id_str_pair::lookup(symm_alg_map, alg, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.hash_algs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "hashes", jsoarr); + for (auto alg : prefs.hash_algs) { + const char * name = id_str_pair::lookup(hash_alg_map, alg, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.z_algs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "compression", jsoarr); + for (auto alg : prefs.z_algs) { + const char * name = id_str_pair::lookup(compress_alg_map, alg, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.ks_prefs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "key server preferences", jsoarr); + for (auto flag : prefs.ks_prefs) { + const char * name = id_str_pair::lookup(key_server_prefs_map, flag, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.key_server.empty()) { + if (!add_json_string_field(jso, "key server", prefs.key_server.c_str())) { + return false; + } + } + return true; +} + +static rnp_result_t +add_json_subsig(json_object *jso, bool is_sub, uint32_t flags, const pgp_subsig_t *subsig) +{ + // userid (if applicable) + if (!is_sub) { + json_object *jsouid = json_object_new_int(subsig->uid); + if (!jsouid) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "userid", jsouid); + } + // trust + json_object *jsotrust = json_object_new_object(); + if (!jsotrust) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "trust", jsotrust); + // trust (level) + json_object *jsotrust_level = json_object_new_int(subsig->trustlevel); + if (!jsotrust_level) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsotrust, "level", jsotrust_level); + // trust (amount) + json_object *jsotrust_amount = json_object_new_int(subsig->trustamount); + if (!jsotrust_amount) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsotrust, "amount", jsotrust_amount); + // key flags (usage) + if (!add_json_key_usage(jso, subsig->key_flags)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // key flags (other) + if (!add_json_key_flags(jso, subsig->key_flags)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // preferences + const pgp_user_prefs_t &prefs = subsig->prefs; + if (!prefs.symm_algs.empty() || !prefs.hash_algs.empty() || !prefs.z_algs.empty() || + !prefs.ks_prefs.empty() || !prefs.key_server.empty()) { + json_object *jsoprefs = json_object_new_object(); + if (!jsoprefs) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "preferences", jsoprefs); + if (!add_json_user_prefs(jsoprefs, prefs)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + const pgp_signature_t *sig = &subsig->sig; + // version + json_object *jsoversion = json_object_new_int(sig->version); + if (!jsoversion) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "version", jsoversion); + // signature type + auto type = id_str_pair::lookup(sig_type_map, sig->type()); + if (!add_json_string_field(jso, "type", type)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // signer key type + const char *key_type = id_str_pair::lookup(pubkey_alg_map, sig->palg); + if (!add_json_string_field(jso, "key type", key_type)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // hash + const char *hash = id_str_pair::lookup(hash_alg_map, sig->halg); + if (!add_json_string_field(jso, "hash", hash)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // creation time + json_object *jsocreation_time = json_object_new_int64(sig->creation()); + if (!jsocreation_time) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "creation time", jsocreation_time); + // expiration (seconds) + json_object *jsoexpiration = json_object_new_int64(sig->expiration()); + if (!jsoexpiration) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "expiration", jsoexpiration); + // signer + json_object *jsosigner = NULL; + // TODO: add signer fingerprint as well (no support internally yet) + if (sig->has_keyid()) { + jsosigner = json_object_new_object(); + if (!jsosigner) { + return RNP_ERROR_OUT_OF_MEMORY; + } + char keyid[PGP_KEY_ID_SIZE * 2 + 1]; + pgp_key_id_t signer = sig->keyid(); + if (!rnp::hex_encode(signer.data(), signer.size(), keyid, sizeof(keyid))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jsosigner, "keyid", keyid)) { + json_object_put(jsosigner); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + json_object_object_add(jso, "signer", jsosigner); + // mpis + json_object *jsompis = NULL; + if (flags & RNP_JSON_SIGNATURE_MPIS) { + jsompis = json_object_new_object(); + if (!jsompis) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t tmpret; + if ((tmpret = add_json_sig_mpis(jsompis, sig))) { + json_object_put(jsompis); + return tmpret; + } + } + json_object_object_add(jso, "mpis", jsompis); + return RNP_SUCCESS; +} + +static rnp_result_t +key_to_json(json_object *jso, rnp_key_handle_t handle, uint32_t flags) +{ + pgp_key_t *key = get_key_prefer_public(handle); + + // type + const char *str = id_str_pair::lookup(pubkey_alg_map, key->alg(), NULL); + if (!str) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!add_json_string_field(jso, "type", str)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // length + if (!add_json_int_field(jso, "length", key->material().bits())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // curve / alg-specific items + switch (key->alg()) { + case PGP_PKA_ECDH: { + const char *hash_name = + id_str_pair::lookup(hash_alg_map, key->material().ec.kdf_hash_alg, NULL); + if (!hash_name) { + return RNP_ERROR_BAD_PARAMETERS; + } + const char *cipher_name = + id_str_pair::lookup(symm_alg_map, key->material().ec.key_wrap_alg, NULL); + if (!cipher_name) { + return RNP_ERROR_BAD_PARAMETERS; + } + json_object *jsohash = json_object_new_string(hash_name); + if (!jsohash) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "kdf hash", jsohash); + json_object *jsocipher = json_object_new_string(cipher_name); + if (!jsocipher) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "key wrap cipher", jsocipher); + } + [[fallthrough]]; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + const char *curve_name = NULL; + if (!curve_type_to_str(key->material().ec.curve, &curve_name)) { + return RNP_ERROR_BAD_PARAMETERS; + } + json_object *jsocurve = json_object_new_string(curve_name); + if (!jsocurve) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "curve", jsocurve); + } break; + default: + break; + } + + // keyid + char keyid[PGP_KEY_ID_SIZE * 2 + 1]; + if (!rnp::hex_encode(key->keyid().data(), key->keyid().size(), keyid, sizeof(keyid))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "keyid", keyid)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // fingerprint + char fpr[PGP_FINGERPRINT_SIZE * 2 + 1]; + if (!rnp::hex_encode(key->fp().fingerprint, key->fp().length, fpr, sizeof(fpr))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "fingerprint", fpr)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // grip + char grip[PGP_KEY_GRIP_SIZE * 2 + 1]; + if (!rnp::hex_encode(key->grip().data(), key->grip().size(), grip, sizeof(grip))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "grip", grip)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // revoked + json_object *jsorevoked = json_object_new_boolean(key->revoked() ? true : false); + if (!jsorevoked) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "revoked", jsorevoked); + // creation time + json_object *jsocreation_time = json_object_new_int64(key->creation()); + if (!jsocreation_time) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "creation time", jsocreation_time); + // expiration + json_object *jsoexpiration = json_object_new_int64(key->expiration()); + if (!jsoexpiration) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "expiration", jsoexpiration); + // key flags (usage) + if (!add_json_key_usage(jso, key->flags())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // key flags (other) + if (!add_json_key_flags(jso, key->flags())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // parent / subkeys + if (key->is_primary()) { + json_object *jsosubkeys_arr = json_object_new_array(); + if (!jsosubkeys_arr) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "subkey grips", jsosubkeys_arr); + for (auto &subfp : key->subkey_fps()) { + const pgp_key_grip_t *subgrip = rnp_get_grip_by_fp(handle->ffi, subfp); + if (!subgrip) { + continue; + } + if (!rnp::hex_encode(subgrip->data(), subgrip->size(), grip, sizeof(grip))) { + return RNP_ERROR_GENERIC; + } + json_object *jsostr = json_object_new_string(grip); + if (!jsostr || json_object_array_add(jsosubkeys_arr, jsostr)) { + json_object_put(jsostr); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + } else if (key->has_primary_fp()) { + auto pgrip = rnp_get_grip_by_fp(handle->ffi, key->primary_fp()); + if (pgrip) { + if (!rnp::hex_encode(pgrip->data(), pgrip->size(), grip, sizeof(grip))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "primary key grip", grip)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + } + // public + json_object *jsopublic = json_object_new_object(); + if (!jsopublic) { + return RNP_ERROR_OUT_OF_MEMORY; + } + bool have_sec = handle->sec != NULL; + bool have_pub = handle->pub != NULL; + json_object_object_add(jso, "public key", jsopublic); + json_object_object_add( + jsopublic, "present", json_object_new_boolean(have_pub ? true : false)); + if (flags & RNP_JSON_PUBLIC_MPIS) { + json_object *jsompis = json_object_new_object(); + if (!jsompis) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsopublic, "mpis", jsompis); + rnp_result_t tmpret; + if ((tmpret = add_json_public_mpis(jsompis, key))) { + return tmpret; + } + } + // secret + json_object *jsosecret = json_object_new_object(); + if (!jsosecret) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "secret key", jsosecret); + json_object_object_add( + jsosecret, "present", json_object_new_boolean(have_sec ? true : false)); + if (have_sec) { + bool locked = handle->sec->is_locked(); + if (flags & RNP_JSON_SECRET_MPIS) { + if (locked) { + json_object_object_add(jsosecret, "mpis", NULL); + } else { + json_object *jsompis = json_object_new_object(); + if (!jsompis) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsosecret, "mpis", jsompis); + rnp_result_t tmpret; + if ((tmpret = add_json_secret_mpis(jsompis, handle->sec))) { + return tmpret; + } + } + } + json_object *jsolocked = json_object_new_boolean(locked ? true : false); + if (!jsolocked) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsosecret, "locked", jsolocked); + json_object *jsoprotected = + json_object_new_boolean(handle->sec->is_protected() ? true : false); + if (!jsoprotected) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsosecret, "protected", jsoprotected); + } + // userids + if (key->is_primary()) { + json_object *jsouids_arr = json_object_new_array(); + if (!jsouids_arr) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "userids", jsouids_arr); + for (size_t i = 0; i < key->uid_count(); i++) { + json_object *jsouid = json_object_new_string(key->get_uid(i).str.c_str()); + if (!jsouid || json_object_array_add(jsouids_arr, jsouid)) { + json_object_put(jsouid); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + } + // signatures + if (flags & RNP_JSON_SIGNATURES) { + json_object *jsosigs_arr = json_object_new_array(); + if (!jsosigs_arr) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "signatures", jsosigs_arr); + for (size_t i = 0; i < key->sig_count(); i++) { + json_object *jsosig = json_object_new_object(); + if (!jsosig || json_object_array_add(jsosigs_arr, jsosig)) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t tmpret; + if ((tmpret = + add_json_subsig(jsosig, key->is_subkey(), flags, &key->get_sig(i)))) { + return tmpret; + } + } + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_to_json(rnp_key_handle_t handle, uint32_t flags, char **result) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + json_object *jso = NULL; + + // checks + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + jso = json_object_new_object(); + if (!jso) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + if ((ret = key_to_json(jso, handle, flags))) { + goto done; + } + *result = (char *) json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY); + if (!*result) { + goto done; + } + *result = strdup(*result); + if (!*result) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + ret = RNP_SUCCESS; +done: + json_object_put(jso); + return ret; +} +FFI_GUARD + +static rnp_result_t +rnp_dump_src_to_json(pgp_source_t *src, uint32_t flags, char **result) +{ + rnp_dump_ctx_t dumpctx = {}; + + dumpctx.dump_mpi = extract_flag(flags, RNP_JSON_DUMP_MPI); + dumpctx.dump_packets = extract_flag(flags, RNP_JSON_DUMP_RAW); + dumpctx.dump_grips = extract_flag(flags, RNP_JSON_DUMP_GRIP); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + json_object *jso = NULL; + rnp_result_t ret = stream_dump_packets_json(&dumpctx, src, &jso); + if (ret) { + goto done; + } + + *result = (char *) json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY); + if (!*result) { + goto done; + } + *result = strdup(*result); + if (!*result) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + ret = RNP_SUCCESS; +done: + json_object_put(jso); + return ret; +} + +rnp_result_t +rnp_key_packets_to_json(rnp_key_handle_t handle, bool secret, uint32_t flags, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = secret ? handle->sec : handle->pub; + if (!key || (key->format == PGP_KEY_STORE_G10)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + auto vec = rnp_key_to_vec(*key); + rnp::MemorySource mem(vec); + return rnp_dump_src_to_json(&mem.src(), flags, result); +} +FFI_GUARD + +rnp_result_t +rnp_dump_packets_to_json(rnp_input_t input, uint32_t flags, char **result) +try { + if (!input || !result) { + return RNP_ERROR_NULL_POINTER; + } + + return rnp_dump_src_to_json(&input->src, flags, result); +} +FFI_GUARD + +rnp_result_t +rnp_dump_packets_to_output(rnp_input_t input, rnp_output_t output, uint32_t flags) +try { + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + rnp_dump_ctx_t dumpctx = {}; + dumpctx.dump_mpi = extract_flag(flags, RNP_DUMP_MPI); + dumpctx.dump_packets = extract_flag(flags, RNP_DUMP_RAW); + dumpctx.dump_grips = extract_flag(flags, RNP_DUMP_GRIP); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = stream_dump_packets(&dumpctx, &input->src, &output->dst); + output->keep = true; + return ret; +} +FFI_GUARD + +// move to next key +static bool +key_iter_next_key(rnp_identifier_iterator_t it) +{ + // check if we not reached the end of the ring + *it->keyp = std::next(*it->keyp); + if (*it->keyp != it->store->keys.end()) { + it->uididx = 0; + return true; + } + // if we are currently on pubring, switch to secring (if not empty) + if (it->store == it->ffi->pubring && !it->ffi->secring->keys.empty()) { + it->store = it->ffi->secring; + *it->keyp = it->store->keys.begin(); + it->uididx = 0; + return true; + } + // we've gone through both rings + it->store = NULL; + return false; +} + +// move to next item (key or userid) +static bool +key_iter_next_item(rnp_identifier_iterator_t it) +{ + switch (it->type) { + case PGP_KEY_SEARCH_KEYID: + case PGP_KEY_SEARCH_FINGERPRINT: + case PGP_KEY_SEARCH_GRIP: + return key_iter_next_key(it); + case PGP_KEY_SEARCH_USERID: + it->uididx++; + while (it->uididx >= (*it->keyp)->uid_count()) { + if (!key_iter_next_key(it)) { + return false; + } + it->uididx = 0; + } + break; + default: + assert(false); + break; + } + return true; +} + +static bool +key_iter_first_key(rnp_identifier_iterator_t it) +{ + if (rnp_key_store_get_key_count(it->ffi->pubring)) { + it->store = it->ffi->pubring; + } else if (rnp_key_store_get_key_count(it->ffi->secring)) { + it->store = it->ffi->secring; + } else { + it->store = NULL; + return false; + } + *it->keyp = it->store->keys.begin(); + it->uididx = 0; + return true; +} + +static bool +key_iter_first_item(rnp_identifier_iterator_t it) +{ + switch (it->type) { + case PGP_KEY_SEARCH_KEYID: + case PGP_KEY_SEARCH_FINGERPRINT: + case PGP_KEY_SEARCH_GRIP: + return key_iter_first_key(it); + case PGP_KEY_SEARCH_USERID: + if (!key_iter_first_key(it)) { + return false; + } + it->uididx = 0; + while (it->uididx >= (*it->keyp)->uid_count()) { + if (!key_iter_next_key(it)) { + return false; + } + } + break; + default: + assert(false); + break; + } + return true; +} + +static bool +key_iter_get_item(const rnp_identifier_iterator_t it, char *buf, size_t buf_len) +{ + const pgp_key_t *key = &**it->keyp; + switch (it->type) { + case PGP_KEY_SEARCH_KEYID: { + if (!rnp::hex_encode(key->keyid().data(), key->keyid().size(), buf, buf_len)) { + return false; + } + break; + } + case PGP_KEY_SEARCH_FINGERPRINT: + if (!rnp::hex_encode(key->fp().fingerprint, key->fp().length, buf, buf_len)) { + return false; + } + break; + case PGP_KEY_SEARCH_GRIP: + if (!rnp::hex_encode(key->grip().data(), key->grip().size(), buf, buf_len)) { + return false; + } + break; + case PGP_KEY_SEARCH_USERID: { + if (it->uididx >= key->uid_count()) { + return false; + } + const pgp_userid_t &uid = key->get_uid(it->uididx); + if (uid.str.size() >= buf_len) { + return false; + } + memcpy(buf, uid.str.c_str(), uid.str.size() + 1); + } break; + default: + assert(false); + break; + } + return true; +} + +rnp_result_t +rnp_identifier_iterator_create(rnp_ffi_t ffi, + rnp_identifier_iterator_t *it, + const char * identifier_type) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + struct rnp_identifier_iterator_st *obj = NULL; + + // checks + if (!ffi || !it || !identifier_type) { + return RNP_ERROR_NULL_POINTER; + } + // create iterator + obj = (struct rnp_identifier_iterator_st *) calloc(1, sizeof(*obj)); + if (!obj) { + return RNP_ERROR_OUT_OF_MEMORY; + } + obj->ffi = ffi; + obj->keyp = new std::list<pgp_key_t>::iterator(); + obj->uididx = 0; + // parse identifier type + obj->type = static_cast<pgp_key_search_type_t>( + id_str_pair::lookup(identifier_type_map, identifier_type, PGP_KEY_SEARCH_UNKNOWN)); + if (obj->type == PGP_KEY_SEARCH_UNKNOWN) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + obj->tbl = json_object_new_object(); + if (!obj->tbl) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + // move to first item (if any) + key_iter_first_item(obj); + *it = obj; + + ret = RNP_SUCCESS; +done: + if (ret) { + rnp_identifier_iterator_destroy(obj); + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_identifier_iterator_next(rnp_identifier_iterator_t it, const char **identifier) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + + // checks + if (!it || !identifier) { + return RNP_ERROR_NULL_POINTER; + } + // initialize the result to NULL + *identifier = NULL; + // this means we reached the end of the rings + if (!it->store) { + return RNP_SUCCESS; + } + // get the item + if (!key_iter_get_item(it, it->buf, sizeof(it->buf))) { + return RNP_ERROR_GENERIC; + } + bool exists; + bool iterator_valid = true; + while ((exists = json_object_object_get_ex(it->tbl, it->buf, NULL))) { + if (!((iterator_valid = key_iter_next_item(it)))) { + break; + } + if (!key_iter_get_item(it, it->buf, sizeof(it->buf))) { + return RNP_ERROR_GENERIC; + } + } + // see if we actually found a new entry + if (!exists) { + // TODO: Newer json-c has a useful return value for json_object_object_add, + // which doesn't require the json_object_object_get_ex check below. + json_object_object_add(it->tbl, it->buf, NULL); + if (!json_object_object_get_ex(it->tbl, it->buf, NULL)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + *identifier = it->buf; + } + // prepare for the next one + if (iterator_valid) { + key_iter_next_item(it); + } + ret = RNP_SUCCESS; + +done: + if (ret) { + *identifier = NULL; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_identifier_iterator_destroy(rnp_identifier_iterator_t it) +try { + if (it) { + json_object_put(it->tbl); + if (it->keyp) { + delete it->keyp; + } + free(it); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_guess_contents(rnp_input_t input, char **contents) +try { + if (!input || !contents) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_armored_msg_t msgtype = PGP_ARMORED_UNKNOWN; + if (is_armored_source(&input->src)) { + msgtype = rnp_armored_get_type(&input->src); + } else { + msgtype = rnp_armor_guess_type(&input->src); + } + const char *msg = id_str_pair::lookup(armor_type_map, msgtype); + size_t len = strlen(msg); + *contents = (char *) calloc(1, len + 1); + if (!*contents) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*contents, msg, len); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_enarmor(rnp_input_t input, rnp_output_t output, const char *type) +try { + pgp_armored_msg_t msgtype = PGP_ARMORED_UNKNOWN; + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + if (type) { + msgtype = static_cast<pgp_armored_msg_t>( + id_str_pair::lookup(armor_type_map, type, PGP_ARMORED_UNKNOWN)); + if (msgtype == PGP_ARMORED_UNKNOWN) { + RNP_LOG("Unsupported armor type: %s", type); + return RNP_ERROR_BAD_PARAMETERS; + } + } else { + msgtype = rnp_armor_guess_type(&input->src); + if (!msgtype) { + RNP_LOG("Unrecognized data to armor (try specifying a type)"); + return RNP_ERROR_BAD_PARAMETERS; + } + } + rnp_result_t ret = rnp_armor_source(&input->src, &output->dst, msgtype); + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_dearmor(rnp_input_t input, rnp_output_t output) +try { + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + rnp_result_t ret = rnp_dearmor_source(&input->src, &output->dst); + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_output_pipe(rnp_input_t input, rnp_output_t output) +try { + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + rnp_result_t ret = dst_write_src(&input->src, &output->dst); + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_output_armor_set_line_length(rnp_output_t output, size_t llen) +try { + if (!output || !llen) { + return RNP_ERROR_BAD_PARAMETERS; + } + return armored_dst_set_line_length(&output->dst, llen); +} +FFI_GUARD + +const char * +rnp_backend_string() +{ + return rnp::backend_string(); +} + +const char * +rnp_backend_version() +{ + return rnp::backend_version(); +} diff --git a/src/lib/sec_profile.cpp b/src/lib/sec_profile.cpp new file mode 100644 index 0000000..f9d0de8 --- /dev/null +++ b/src/lib/sec_profile.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2021 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "sec_profile.hpp" +#include "types.h" +#include "defaults.h" +#include <ctime> +#include <algorithm> + +namespace rnp { +bool +SecurityRule::operator==(const SecurityRule &src) const +{ + return (type == src.type) && (feature == src.feature) && (from == src.from) && + (level == src.level) && (override == src.override) && (action == src.action); +} + +bool +SecurityRule::operator!=(const SecurityRule &src) const +{ + return !(*this == src); +} + +bool +SecurityRule::matches(FeatureType ftype, + int fval, + uint64_t ftime, + SecurityAction faction) const noexcept +{ + if ((type != ftype) || (feature != fval) || (from > ftime)) { + return false; + } + return (action == SecurityAction::Any) || (faction == SecurityAction::Any) || + (action == faction); +} + +size_t +SecurityProfile::size() const noexcept +{ + return rules_.size(); +} + +SecurityRule & +SecurityProfile::add_rule(const SecurityRule &rule) +{ + rules_.push_back(rule); + return rules_.back(); +} + +SecurityRule & +SecurityProfile::add_rule(SecurityRule &&rule) +{ + rules_.emplace_back(rule); + return rules_.back(); +} + +bool +SecurityProfile::del_rule(const SecurityRule &rule) +{ + size_t old_size = rules_.size(); + rules_.erase(std::remove_if(rules_.begin(), + rules_.end(), + [rule](const SecurityRule &item) { return item == rule; }), + rules_.end()); + return old_size != rules_.size(); +} + +void +SecurityProfile::clear_rules(FeatureType type, int feature) +{ + rules_.erase(std::remove_if(rules_.begin(), + rules_.end(), + [type, feature](const SecurityRule &item) { + return (item.type == type) && (item.feature == feature); + }), + rules_.end()); +} + +void +SecurityProfile::clear_rules(FeatureType type) +{ + rules_.erase( + std::remove_if(rules_.begin(), + rules_.end(), + [type](const SecurityRule &item) { return item.type == type; }), + rules_.end()); +} + +void +SecurityProfile::clear_rules() +{ + rules_.clear(); +} + +bool +SecurityProfile::has_rule(FeatureType type, + int value, + uint64_t time, + SecurityAction action) const noexcept +{ + for (auto &rule : rules_) { + if (rule.matches(type, value, time, action)) { + return true; + } + } + return false; +} + +const SecurityRule & +SecurityProfile::get_rule(FeatureType type, + int value, + uint64_t time, + SecurityAction action) const +{ + const SecurityRule *res = nullptr; + for (auto &rule : rules_) { + if (!rule.matches(type, value, time, action)) { + continue; + } + if (rule.override) { + return rule; + } + if (!res || (res->from < rule.from)) { + res = &rule; + } + } + if (!res) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return *res; +} + +SecurityLevel +SecurityProfile::hash_level(pgp_hash_alg_t hash, + uint64_t time, + SecurityAction action) const noexcept +{ + if (!has_rule(FeatureType::Hash, hash, time, action)) { + return def_level(); + } + + try { + return get_rule(FeatureType::Hash, hash, time, action).level; + } catch (const std::exception &e) { + /* this should never happen however we need to satisfy noexcept specifier */ + return def_level(); + } +} + +SecurityLevel +SecurityProfile::def_level() const +{ + return SecurityLevel::Default; +}; + +SecurityContext::SecurityContext() : time_(0), prov_state_(NULL), rng(RNG::Type::DRBG) +{ + /* Initialize crypto provider if needed (currently only for OpenSSL 3.0) */ + if (!rnp::backend_init(&prov_state_)) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + /* Mark SHA-1 data signature insecure since 2019-01-19, as GnuPG does */ + profile.add_rule({FeatureType::Hash, + PGP_HASH_SHA1, + SecurityLevel::Insecure, + 1547856000, + SecurityAction::VerifyData}); + /* Mark SHA-1 key signature insecure since 2024-01-19 by default */ + profile.add_rule({FeatureType::Hash, + PGP_HASH_SHA1, + SecurityLevel::Insecure, + 1705629600, + SecurityAction::VerifyKey}); + /* Mark MD5 insecure since 2012-01-01 */ + profile.add_rule({FeatureType::Hash, PGP_HASH_MD5, SecurityLevel::Insecure, 1325376000}); +} + +SecurityContext::~SecurityContext() +{ + rnp::backend_finish(prov_state_); +} + +size_t +SecurityContext::s2k_iterations(pgp_hash_alg_t halg) +{ + if (!s2k_iterations_.count(halg)) { + s2k_iterations_[halg] = + pgp_s2k_compute_iters(halg, DEFAULT_S2K_MSEC, DEFAULT_S2K_TUNE_MSEC); + } + return s2k_iterations_[halg]; +} + +void +SecurityContext::set_time(uint64_t time) noexcept +{ + time_ = time; +} + +uint64_t +SecurityContext::time() const noexcept +{ + return time_ ? time_ : ::time(NULL); +} + +} // namespace rnp diff --git a/src/lib/sec_profile.hpp b/src/lib/sec_profile.hpp new file mode 100644 index 0000000..a4d8456 --- /dev/null +++ b/src/lib/sec_profile.hpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_SEC_PROFILE_H_ +#define RNP_SEC_PROFILE_H_ + +#include <cstdint> +#include <vector> +#include <unordered_map> +#include "repgp/repgp_def.h" +#include "crypto/rng.h" + +namespace rnp { + +enum class FeatureType { Hash, Cipher, PublicKey }; +enum class SecurityLevel { Disabled, Insecure, Default }; +enum class SecurityAction { Any, VerifyKey, VerifyData }; + +struct SecurityRule { + FeatureType type; + int feature; + SecurityLevel level; + uint64_t from; + bool override; + SecurityAction action; + + SecurityRule(FeatureType ftype, + int fval, + SecurityLevel flevel, + uint64_t ffrom = 0, + SecurityAction faction = SecurityAction::Any) + : type(ftype), feature(fval), level(flevel), from(ffrom), override(false), + action(faction){}; + + bool operator==(const SecurityRule &src) const; + bool operator!=(const SecurityRule &src) const; + + bool matches(FeatureType ftype, + int fval, + uint64_t ftime, + SecurityAction faction) const noexcept; +}; + +class SecurityProfile { + private: + std::vector<SecurityRule> rules_; + + public: + size_t size() const noexcept; + SecurityRule &add_rule(const SecurityRule &rule); + SecurityRule &add_rule(SecurityRule &&rule); + bool del_rule(const SecurityRule &rule); + void clear_rules(FeatureType type, int feature); + void clear_rules(FeatureType type); + void clear_rules(); + + bool has_rule(FeatureType type, + int value, + uint64_t time, + SecurityAction action = SecurityAction::Any) const noexcept; + const SecurityRule &get_rule(FeatureType type, + int value, + uint64_t time, + SecurityAction action = SecurityAction::Any) const; + SecurityLevel hash_level(pgp_hash_alg_t hash, + uint64_t time, + SecurityAction action = SecurityAction::Any) const noexcept; + SecurityLevel def_level() const; +}; + +class SecurityContext { + std::unordered_map<int, size_t> s2k_iterations_; + uint64_t time_; + void * prov_state_; + + public: + SecurityProfile profile; + RNG rng; + + SecurityContext(); + ~SecurityContext(); + + size_t s2k_iterations(pgp_hash_alg_t halg); + + void set_time(uint64_t time) noexcept; + uint64_t time() const noexcept; +}; +} // namespace rnp + +#endif diff --git a/src/lib/types.h b/src/lib/types.h new file mode 100644 index 0000000..5a67d42 --- /dev/null +++ b/src/lib/types.h @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TYPES_H_ +#define TYPES_H_ + +#include <stdint.h> +#include <string> +#include <vector> +#include <array> +#include <cstring> +#include <type_traits> + +#include <rnp/rnp_def.h> +#include "crypto/common.h" +#include "sec_profile.hpp" + +/* SHA1 Hash Size */ +#define PGP_SHA1_HASH_SIZE 20 + +/* Maximum length of the packet header */ +#define PGP_MAX_HEADER_SIZE 6 + +/* Maximum supported userid length */ +#define MAX_ID_LENGTH 128 + +/* Maximum supported password length */ +#define MAX_PASSWORD_LENGTH 256 + +class id_str_pair { + public: + int id; + const char *str; + + /** + * @brief Lookup constant pair array for the specified id or string value. + * Note: array must be finished with NULL string to stop the lookup. + * + * @param pair pointer to the const array with pairs. + * @param id identifier to search for + * @param notfound value to return if identifier is not found. + * @return string, representing the identifier. + */ + static const char *lookup(const id_str_pair pair[], + int id, + const char * notfound = "unknown"); + static int lookup(const id_str_pair pair[], const char *str, int notfound = 0); + static int lookup(const id_str_pair pair[], + const std::vector<uint8_t> &bytes, + int notfound = 0); + static int lookup(const id_str_pair pair[], + const std::basic_string<uint8_t> &bytes, + int notfound = 0); +}; + +/** pgp_fingerprint_t */ +typedef struct pgp_fingerprint_t { + uint8_t fingerprint[PGP_FINGERPRINT_SIZE]; + unsigned length; + bool operator==(const pgp_fingerprint_t &src) const; + bool operator!=(const pgp_fingerprint_t &src) const; +} pgp_fingerprint_t; + +typedef std::array<uint8_t, PGP_KEY_GRIP_SIZE> pgp_sig_id_t; + +namespace std { +template <> struct hash<pgp_fingerprint_t> { + std::size_t + operator()(pgp_fingerprint_t const &fp) const noexcept + { + /* since fingerprint value is hash itself, we may use its low bytes */ + size_t res = 0; + static_assert(sizeof(fp.fingerprint) == PGP_FINGERPRINT_SIZE, + "pgp_fingerprint_t size mismatch"); + static_assert(PGP_FINGERPRINT_SIZE >= sizeof(res), "pgp_fingerprint_t size mismatch"); + std::memcpy(&res, fp.fingerprint, sizeof(res)); + return res; + } +}; + +template <> struct hash<pgp_sig_id_t> { + std::size_t + operator()(pgp_sig_id_t const &sigid) const noexcept + { + /* since signature id value is hash itself, we may use its low bytes */ + size_t res = 0; + static_assert(std::tuple_size<pgp_sig_id_t>::value >= sizeof(res), + "pgp_sig_id_t size mismatch"); + std::memcpy(&res, sigid.data(), sizeof(res)); + return res; + } +}; +}; // namespace std + +typedef std::array<uint8_t, PGP_KEY_GRIP_SIZE> pgp_key_grip_t; + +typedef std::array<uint8_t, PGP_KEY_ID_SIZE> pgp_key_id_t; + +namespace rnp { +class rnp_exception : public std::exception { + rnp_result_t code_; + + public: + rnp_exception(rnp_result_t code = RNP_ERROR_GENERIC) : code_(code){}; + virtual const char * + what() const throw() + { + return "rnp_exception"; + }; + rnp_result_t + code() const + { + return code_; + }; +}; +} // namespace rnp + +/* validity information for the signature/key/userid */ +typedef struct pgp_validity_t { + bool validated{}; /* item was validated */ + bool valid{}; /* item is valid by signature/key checks and calculations. + Still may be revoked or expired. */ + bool expired{}; /* item is expired */ + + void mark_valid(); + void reset(); +} pgp_validity_t; + +/** + * Type to keep public/secret key mpis without any openpgp-dependent data. + */ +typedef struct pgp_key_material_t { + pgp_pubkey_alg_t alg; /* algorithm of the key */ + bool secret; /* secret part of the key material is populated */ + pgp_validity_t validity; /* key material validation status */ + + union { + pgp_rsa_key_t rsa; + pgp_dsa_key_t dsa; + pgp_eg_key_t eg; + pgp_ec_key_t ec; + }; + + size_t bits() const; + size_t qbits() const; + void validate(rnp::SecurityContext &ctx, bool reset = true); + bool valid() const; +} pgp_key_material_t; + +/** + * Type to keep signature without any openpgp-dependent data. + */ +typedef struct pgp_signature_material_t { + union { + pgp_rsa_signature_t rsa; + pgp_dsa_signature_t dsa; + pgp_ec_signature_t ecc; + pgp_eg_signature_t eg; + }; +} pgp_signature_material_t; + +/** + * Type to keep pk-encrypted data without any openpgp-dependent data. + */ +typedef struct pgp_encrypted_material_t { + union { + pgp_rsa_encrypted_t rsa; + pgp_eg_encrypted_t eg; + pgp_sm2_encrypted_t sm2; + pgp_ecdh_encrypted_t ecdh; + }; +} pgp_encrypted_material_t; + +typedef struct pgp_s2k_t { + pgp_s2k_usage_t usage{}; + + /* below fields may not all be valid, depending on the usage field above */ + pgp_s2k_specifier_t specifier{}; + pgp_hash_alg_t hash_alg{}; + uint8_t salt[PGP_SALT_SIZE]; + unsigned iterations{}; + /* GnuPG custom s2k data */ + pgp_s2k_gpg_extension_t gpg_ext_num{}; + uint8_t gpg_serial_len{}; + uint8_t gpg_serial[16]; + /* Experimental s2k data */ + std::vector<uint8_t> experimental{}; +} pgp_s2k_t; + +typedef struct pgp_key_protection_t { + pgp_s2k_t s2k{}; /* string-to-key kdf params */ + pgp_symm_alg_t symm_alg{}; /* symmetric alg */ + pgp_cipher_mode_t cipher_mode{}; /* block cipher mode */ + uint8_t iv[PGP_MAX_BLOCK_SIZE]; +} pgp_key_protection_t; + +typedef struct pgp_key_pkt_t pgp_key_pkt_t; +typedef struct pgp_userid_pkt_t pgp_userid_pkt_t; +typedef struct pgp_signature_t pgp_signature_t; + +/* Signature subpacket, see 5.2.3.1 in RFC 4880 and RFC 4880 bis 02 */ +typedef struct pgp_sig_subpkt_t { + pgp_sig_subpacket_type_t type; /* type of the subpacket */ + size_t len; /* length of the data */ + uint8_t * data; /* raw subpacket data, excluding the header */ + bool critical : 1; /* critical flag */ + bool hashed : 1; /* whether subpacket is hashed or not */ + bool parsed : 1; /* whether subpacket was successfully parsed */ + union { + uint32_t create; /* 5.2.3.4. Signature Creation Time */ + uint32_t expiry; /* 5.2.3.6. Key Expiration Time */ + /* 5.2.3.10. Signature Expiration Time */ + bool exportable; /* 5.2.3.11. Exportable Certification */ + struct { + uint8_t level; + uint8_t amount; + } trust; /* 5.2.3.13. Trust Signature */ + struct { + const char *str; + unsigned len; + } regexp; /* 5.2.3.14. Regular Expression */ + bool revocable; /* 5.2.3.12. Revocable */ + struct { + uint8_t *arr; + unsigned len; + } preferred; /* 5.2.3.7. Preferred Symmetric Algorithms */ + /* 5.2.3.8. Preferred Hash Algorithms */ + /* 5.2.3.9. Preferred Compression Algorithms */ + struct { + uint8_t klass; + pgp_pubkey_alg_t pkalg; + uint8_t * fp; + } revocation_key; /* 5.2.3.15. Revocation Key */ + uint8_t *issuer; /* 5.2.3.5. Issuer */ + struct { + uint8_t flags[4]; + unsigned nlen; + unsigned vlen; + bool human; + const uint8_t *name; + const uint8_t *value; + } notation; /* 5.2.3.16. Notation Data */ + struct { + bool no_modify; + } ks_prefs; /* 5.2.3.17. Key Server Preferences */ + struct { + const char *uri; + unsigned len; + } preferred_ks; /* 5.2.3.18. Preferred Key Server */ + bool primary_uid; /* 5.2.3.19. Primary User ID */ + struct { + const char *uri; + unsigned len; + } policy; /* 5.2.3.20. Policy URI */ + uint8_t key_flags; /* 5.2.3.21. Key Flags */ + struct { + const char *uid; + unsigned len; + } signer; /* 5.2.3.22. Signer's User ID */ + struct { + pgp_revocation_type_t code; + const char * str; + unsigned len; + } revocation_reason; /* 5.2.3.23. Reason for Revocation */ + uint8_t features; /* 5.2.3.24. Features */ + struct { + pgp_pubkey_alg_t pkalg; + pgp_hash_alg_t halg; + uint8_t * hash; + unsigned hlen; + } sig_target; /* 5.2.3.25. Signature Target */ + pgp_signature_t *sig; /* 5.2.3.27. Embedded Signature */ + struct { + uint8_t version; + uint8_t *fp; + unsigned len; + } issuer_fp; /* 5.2.3.28. Issuer Fingerprint, RFC 4880 bis 04 */ + } fields; /* parsed contents of the subpacket */ + + pgp_sig_subpkt_t() + : type(PGP_SIG_SUBPKT_UNKNOWN), len(0), data(NULL), critical(false), hashed(false), + parsed(false), fields({}){}; + pgp_sig_subpkt_t(const pgp_sig_subpkt_t &src); + pgp_sig_subpkt_t(pgp_sig_subpkt_t &&src); + pgp_sig_subpkt_t &operator=(pgp_sig_subpkt_t &&src); + pgp_sig_subpkt_t &operator=(const pgp_sig_subpkt_t &src); + ~pgp_sig_subpkt_t(); + bool parse(); +} pgp_sig_subpkt_t; + +typedef struct pgp_one_pass_sig_t pgp_one_pass_sig_t; + +typedef enum { + /* first octet */ + PGP_KEY_SERVER_NO_MODIFY = 0x80 +} pgp_key_server_prefs_t; + +typedef struct pgp_literal_hdr_t { + uint8_t format; + char fname[256]; + uint8_t fname_len; + uint32_t timestamp; +} pgp_literal_hdr_t; + +typedef struct pgp_aead_hdr_t { + int version{}; /* version of the AEAD packet */ + pgp_symm_alg_t ealg; /* underlying symmetric algorithm */ + pgp_aead_alg_t aalg; /* AEAD algorithm, i.e. EAX, OCB, etc */ + int csize{}; /* chunk size bits */ + uint8_t iv[PGP_AEAD_MAX_NONCE_LEN]; /* initial vector for the message */ + size_t ivlen{}; /* iv length */ + + pgp_aead_hdr_t() : ealg(PGP_SA_UNKNOWN), aalg(PGP_AEAD_NONE) + { + } +} pgp_aead_hdr_t; + +/** litdata_type_t */ +typedef enum { + PGP_LDT_BINARY = 'b', + PGP_LDT_TEXT = 't', + PGP_LDT_UTF8 = 'u', + PGP_LDT_LOCAL = 'l', + PGP_LDT_LOCAL2 = '1' +} pgp_litdata_enum; + +/* user revocation info */ +typedef struct pgp_subsig_t pgp_subsig_t; + +typedef struct pgp_revoke_t { + uint32_t uid{}; /* index in uid array */ + pgp_revocation_type_t code{}; /* revocation code */ + std::string reason; /* revocation reason */ + pgp_sig_id_t sigid{}; /* id of the corresponding subsig */ + + pgp_revoke_t() = default; + pgp_revoke_t(pgp_subsig_t &sig); +} pgp_revoke_t; + +typedef struct pgp_user_prefs_t { + // preferred symmetric algs (pgp_symm_alg_t) + std::vector<uint8_t> symm_algs{}; + // preferred hash algs (pgp_hash_alg_t) + std::vector<uint8_t> hash_algs{}; + // preferred compression algs (pgp_compression_type_t) + std::vector<uint8_t> z_algs{}; + // key server preferences (pgp_key_server_prefs_t) + std::vector<uint8_t> ks_prefs{}; + // preferred key server + std::string key_server{}; + + void set_symm_algs(const std::vector<uint8_t> &algs); + void add_symm_alg(pgp_symm_alg_t alg); + void set_hash_algs(const std::vector<uint8_t> &algs); + void add_hash_alg(pgp_hash_alg_t alg); + void set_z_algs(const std::vector<uint8_t> &algs); + void add_z_alg(pgp_compression_type_t alg); + void set_ks_prefs(const std::vector<uint8_t> &prefs); + void add_ks_pref(pgp_key_server_prefs_t pref); +} pgp_user_prefs_t; + +struct rnp_keygen_ecc_params_t { + pgp_curve_t curve; +}; + +struct rnp_keygen_rsa_params_t { + uint32_t modulus_bit_len; +}; + +struct rnp_keygen_dsa_params_t { + size_t p_bitlen; + size_t q_bitlen; +}; + +struct rnp_keygen_elgamal_params_t { + size_t key_bitlen; +}; + +/* structure used to hold context of key generation */ +namespace rnp { +class SecurityContext; +} + +typedef struct rnp_keygen_crypto_params_t { + // Asymmteric algorithm that user requesed key for + pgp_pubkey_alg_t key_alg; + // Hash to be used for key signature + pgp_hash_alg_t hash_alg; + // Pointer to security context + rnp::SecurityContext *ctx; + union { + struct rnp_keygen_ecc_params_t ecc; + struct rnp_keygen_rsa_params_t rsa; + struct rnp_keygen_dsa_params_t dsa; + struct rnp_keygen_elgamal_params_t elgamal; + }; +} rnp_keygen_crypto_params_t; + +typedef struct rnp_selfsig_cert_info_t { + std::string userid; /* userid, required */ + uint8_t key_flags{}; /* key flags */ + uint32_t key_expiration{}; /* key expiration time (sec), 0 = no expiration */ + pgp_user_prefs_t prefs{}; /* user preferences, optional */ + bool primary; /* mark this as the primary user id */ + + /** + * @brief Populate uid and sig packet with data stored in this struct. + * At some point we should get rid of it. + */ + void populate(pgp_userid_pkt_t &uid, pgp_signature_t &sig); +} rnp_selfsig_cert_info_t; + +typedef struct rnp_selfsig_binding_info_t { + uint8_t key_flags; + uint32_t key_expiration; +} rnp_selfsig_binding_info_t; + +typedef struct rnp_keygen_primary_desc_t { + rnp_keygen_crypto_params_t crypto{}; + rnp_selfsig_cert_info_t cert{}; +} rnp_keygen_primary_desc_t; + +typedef struct rnp_keygen_subkey_desc_t { + rnp_keygen_crypto_params_t crypto; + rnp_selfsig_binding_info_t binding; +} rnp_keygen_subkey_desc_t; + +typedef struct rnp_key_protection_params_t { + pgp_symm_alg_t symm_alg; + pgp_cipher_mode_t cipher_mode; + unsigned iterations; + pgp_hash_alg_t hash_alg; +} rnp_key_protection_params_t; + +#endif /* TYPES_H_ */ diff --git a/src/lib/utils.cpp b/src/lib/utils.cpp new file mode 100644 index 0000000..3c6216c --- /dev/null +++ b/src/lib/utils.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "types.h" +#include "str-utils.h" + +const char * +id_str_pair::lookup(const id_str_pair pair[], int id, const char *notfound) +{ + while (pair && pair->str) { + if (pair->id == id) { + return pair->str; + } + pair++; + } + return notfound; +} + +int +id_str_pair::lookup(const id_str_pair pair[], const char *str, int notfound) +{ + while (pair && pair->str) { + if (rnp::str_case_eq(str, pair->str)) { + return pair->id; + } + pair++; + } + return notfound; +} + +int +id_str_pair::lookup(const id_str_pair pair[], const std::vector<uint8_t> &bytes, int notfound) +{ + while (pair && pair->str) { + if ((strlen(pair->str) == bytes.size()) && + !memcmp(pair->str, bytes.data(), bytes.size())) { + return pair->id; + } + pair++; + } + return notfound; +} + +int +id_str_pair::lookup(const id_str_pair pair[], + const std::basic_string<uint8_t> &bytes, + int notfound) +{ + while (pair && pair->str) { + if ((strlen(pair->str) == bytes.size()) && + !memcmp(pair->str, bytes.data(), bytes.size())) { + return pair->id; + } + pair++; + } + return notfound; +} diff --git a/src/lib/utils.h b/src/lib/utils.h new file mode 100644 index 0000000..3035ee5 --- /dev/null +++ b/src/lib/utils.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2017-2021 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_UTILS_H_ +#define RNP_UTILS_H_ + +#include <stdio.h> +#include <limits.h> +#include "logging.h" + +/* number of elements in an array */ +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) + +/* + * @params + * array: array of the structures to lookup + * id_field name of the field to compare against + * ret_field filed to return + * lookup_value lookup value + * ret return value + */ +#define ARRAY_LOOKUP_BY_ID(array, id_field, ret_field, lookup_value, ret) \ + do { \ + for (size_t i__ = 0; i__ < ARRAY_SIZE(array); i__++) { \ + if ((array)[i__].id_field == (lookup_value)) { \ + (ret) = (array)[i__].ret_field; \ + break; \ + } \ + } \ + } while (0) + +/* Portable way to convert bits to bytes */ + +#define BITS_TO_BYTES(b) (((b) + (CHAR_BIT - 1)) / CHAR_BIT) + +/* Load little-endian 32-bit from y to x in portable fashion */ + +inline void +LOAD32LE(uint32_t &x, const uint8_t y[4]) +{ + x = (static_cast<uint32_t>(y[3]) << 24) | (static_cast<uint32_t>(y[2]) << 16) | + (static_cast<uint32_t>(y[1]) << 8) | (static_cast<uint32_t>(y[0]) << 0); +} + +/* Store big-endian 32-bit value x in y */ +inline void +STORE32BE(uint8_t x[4], uint32_t y) +{ + x[0] = (uint8_t)(y >> 24) & 0xff; + x[1] = (uint8_t)(y >> 16) & 0xff; + x[2] = (uint8_t)(y >> 8) & 0xff; + x[3] = (uint8_t)(y >> 0) & 0xff; +} + +/* Store big-endian 64-bit value x in y */ +inline void +STORE64BE(uint8_t x[8], uint64_t y) +{ + x[0] = (uint8_t)(y >> 56) & 0xff; + x[1] = (uint8_t)(y >> 48) & 0xff; + x[2] = (uint8_t)(y >> 40) & 0xff; + x[3] = (uint8_t)(y >> 32) & 0xff; + x[4] = (uint8_t)(y >> 24) & 0xff; + x[5] = (uint8_t)(y >> 16) & 0xff; + x[6] = (uint8_t)(y >> 8) & 0xff; + x[7] = (uint8_t)(y >> 0) & 0xff; +} + +inline char * +getenv_logname(void) +{ + char *name = getenv("LOGNAME"); + if (!name) { + name = getenv("USER"); + } + return name; +} + +#endif diff --git a/src/lib/version.h.in b/src/lib/version.h.in new file mode 100644 index 0000000..2382cfd --- /dev/null +++ b/src/lib/version.h.in @@ -0,0 +1,52 @@ +/* Copyright (c) 2018 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define RNP_VERSION_MAJOR @RNP_VERSION_MAJOR@ +#define RNP_VERSION_MINOR @RNP_VERSION_MINOR@ +#define RNP_VERSION_PATCH @RNP_VERSION_PATCH@ + +#define RNP_VERSION_STRING "@RNP_VERSION@" +#define RNP_VERSION_STRING_FULL "@RNP_VERSION_FULL@" + +#define RNP_VERSION_COMMIT_TIMESTAMP @RNP_VERSION_COMMIT_TIMESTAMP@ + +// using a 32-bit version with 10 bits per component +#define RNP_VERSION_COMPONENT_MASK 0x3ff +#define RNP_VERSION_MAJOR_SHIFT 20 +#define RNP_VERSION_MINOR_SHIFT 10 +#define RNP_VERSION_PATCH_SHIFT 0 +#define RNP_VERSION_CODE_FOR(major, minor, patch) \ + (((major & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_MAJOR_SHIFT) | \ + ((minor & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_MINOR_SHIFT) | \ + ((patch & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_PATCH_SHIFT)) + +#define RNP_VERSION_CODE \ + RNP_VERSION_CODE_FOR(RNP_VERSION_MAJOR, RNP_VERSION_MINOR, RNP_VERSION_PATCH) + +static_assert(RNP_VERSION_MAJOR <= RNP_VERSION_COMPONENT_MASK && + RNP_VERSION_MINOR <= RNP_VERSION_COMPONENT_MASK && + RNP_VERSION_PATCH <= RNP_VERSION_COMPONENT_MASK, + "version components must be within range"); + diff --git a/src/librekey/g23_sexp.hpp b/src/librekey/g23_sexp.hpp new file mode 100644 index 0000000..b888680 --- /dev/null +++ b/src/librekey/g23_sexp.hpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE RIBOSE, INC. AND CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_G23_SEXP_HPP +#define RNP_G23_SEXP_HPP + +#include "sexp/sexp.h" +#include "sexp/ext-key-format.h" + +#define SXP_MAX_DEPTH 30 + +class gnupg_sexp_t; +typedef std::shared_ptr<gnupg_sexp_t> p_gnupg_sexp; + +class gnupg_sexp_t : public sexp::sexp_list_t { + /* write gnupg_sexp_t contents, adding padding, for the further encryption */ + rnp::secure_vector<uint8_t> write_padded(size_t padblock) const; + + public: + void + add(const std::string &str) + { + push_back(std::shared_ptr<sexp::sexp_string_t>(new sexp::sexp_string_t(str))); + }; + void + add(const uint8_t *data, size_t size) + { + push_back(std::shared_ptr<sexp::sexp_string_t>(new sexp::sexp_string_t(data, size))); + }; + void add(unsigned u); + p_gnupg_sexp add_sub(); + void add_mpi(const std::string &name, const pgp_mpi_t &val); + void add_curve(const std::string &name, const pgp_ec_key_t &key); + void add_pubkey(const pgp_key_pkt_t &key); + void add_seckey(const pgp_key_pkt_t &key); + void add_protected_seckey(pgp_key_pkt_t & seckey, + const std::string & password, + rnp::SecurityContext &ctx); + bool parse(const char *r_bytes, size_t r_length, size_t depth = 1); + bool write(pgp_dest_t &dst) const noexcept; +}; + +class gnupg_extended_private_key_t : public ext_key_format::extended_private_key_t { + public: + bool parse(const char *r_bytes, size_t r_length, size_t depth = 1); +}; + +#endif diff --git a/src/librekey/kbx_blob.hpp b/src/librekey/kbx_blob.hpp new file mode 100644 index 0000000..274413c --- /dev/null +++ b/src/librekey/kbx_blob.hpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE RIBOSE, INC. AND CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_KBX_BLOB_HPP +#define RNP_KBX_BLOB_HPP + +typedef enum : uint8_t { + KBX_EMPTY_BLOB = 0, + KBX_HEADER_BLOB = 1, + KBX_PGP_BLOB = 2, + KBX_X509_BLOB = 3 +} kbx_blob_type_t; + +class kbx_blob_t { + protected: + kbx_blob_type_t type_; + std::vector<uint8_t> image_; + + uint8_t ru8(size_t idx); + uint16_t ru16(size_t idx); + uint32_t ru32(size_t idx); + + public: + virtual ~kbx_blob_t() = default; + kbx_blob_t(std::vector<uint8_t> &data); + virtual bool + parse() + { + return true; + }; + + kbx_blob_type_t + type() + { + return type_; + } + + std::vector<uint8_t> & + image() + { + return image_; + } + + uint32_t + length() const noexcept + { + return image_.size(); + } +}; + +class kbx_header_blob_t : public kbx_blob_t { + protected: + uint8_t version_{}; + uint16_t flags_{}; + uint32_t file_created_at_{}; + uint32_t last_maintenance_run_{}; + + public: + kbx_header_blob_t(std::vector<uint8_t> &data) : kbx_blob_t(data){}; + bool parse(); + + uint32_t + file_created_at() + { + return file_created_at_; + } +}; + +typedef struct { + uint8_t fp[PGP_FINGERPRINT_SIZE]; + uint32_t keyid_offset; + uint16_t flags; +} kbx_pgp_key_t; + +typedef struct { + uint32_t offset; + uint32_t length; + uint16_t flags; + uint8_t validity; +} kbx_pgp_uid_t; + +typedef struct { + uint32_t expired; +} kbx_pgp_sig_t; + +class kbx_pgp_blob_t : public kbx_blob_t { + protected: + uint8_t version_{}; + uint16_t flags_{}; + uint32_t keyblock_offset_{}; + uint32_t keyblock_length_{}; + + std::vector<uint8_t> sn_{}; + std::vector<kbx_pgp_key_t> keys_{}; + std::vector<kbx_pgp_uid_t> uids_{}; + std::vector<kbx_pgp_sig_t> sigs_{}; + + uint8_t ownertrust_{}; + uint8_t all_validity_{}; + + uint32_t recheck_after_{}; + uint32_t latest_timestamp_{}; + uint32_t blob_created_at_{}; + + public: + kbx_pgp_blob_t(std::vector<uint8_t> &data) : kbx_blob_t(data){}; + + uint32_t + keyblock_offset() + { + return keyblock_offset_; + } + + uint32_t + keyblock_length() + { + return keyblock_length_; + } + + size_t + nkeys() + { + return keys_.size(); + } + size_t + nuids() + { + return uids_.size(); + } + size_t + nsigs() + { + return sigs_.size(); + } + + bool parse(); +}; + +#endif diff --git a/src/librekey/key_store_g10.cpp b/src/librekey/key_store_g10.cpp new file mode 100644 index 0000000..dcf3fe1 --- /dev/null +++ b/src/librekey/key_store_g10.cpp @@ -0,0 +1,1243 @@ +/* + * Copyright (c) 2017-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <memory> +#include <sstream> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <time.h> +#include "config.h" + +#include <librepgp/stream-packet.h> +#include "key_store_pgp.h" +#include "key_store_g10.h" + +#include "crypto/common.h" +#include "crypto/mem.h" +#include "crypto/cipher.hpp" +#include "pgp-key.h" +#include "time-utils.h" + +#include "g23_sexp.hpp" +using namespace ext_key_format; +using namespace sexp; + +#define G10_CBC_IV_SIZE 16 + +#define G10_OCB_NONCE_SIZE 12 + +#define G10_SHA1_HASH_SIZE 20 + +#define G10_PROTECTED_AT_SIZE 15 + +typedef struct format_info { + pgp_symm_alg_t cipher; + pgp_cipher_mode_t cipher_mode; + pgp_hash_alg_t hash_alg; + size_t cipher_block_size; + const char * g10_type; + size_t iv_size; + size_t tag_length; + bool with_associated_data; + bool disable_padding; +} format_info; + +static bool g10_calculated_hash(const pgp_key_pkt_t &key, + const char * protected_at, + uint8_t * checksum); + +static const format_info formats[] = {{PGP_SA_AES_128, + PGP_CIPHER_MODE_CBC, + PGP_HASH_SHA1, + 16, + "openpgp-s2k3-sha1-aes-cbc", + G10_CBC_IV_SIZE, + 0, + false, + true}, + {PGP_SA_AES_256, + PGP_CIPHER_MODE_CBC, + PGP_HASH_SHA1, + 16, + "openpgp-s2k3-sha1-aes256-cbc", + G10_CBC_IV_SIZE, + 0, + false, + true}, + {PGP_SA_AES_128, + PGP_CIPHER_MODE_OCB, + PGP_HASH_SHA1, + 16, + "openpgp-s2k3-ocb-aes", + G10_OCB_NONCE_SIZE, + 16, + true, + true}}; + +static const id_str_pair g10_alg_aliases[] = { + {PGP_PKA_RSA, "rsa"}, + {PGP_PKA_RSA, "openpgp-rsa"}, + {PGP_PKA_RSA, "oid.1.2.840.113549.1.1.1"}, + {PGP_PKA_RSA, "oid.1.2.840.113549.1.1.1"}, + {PGP_PKA_ELGAMAL, "elg"}, + {PGP_PKA_ELGAMAL, "elgamal"}, + {PGP_PKA_ELGAMAL, "openpgp-elg"}, + {PGP_PKA_ELGAMAL, "openpgp-elg-sig"}, + {PGP_PKA_DSA, "dsa"}, + {PGP_PKA_DSA, "openpgp-dsa"}, + {PGP_PKA_ECDSA, "ecc"}, + {PGP_PKA_ECDSA, "ecdsa"}, + {PGP_PKA_ECDH, "ecdh"}, + {PGP_PKA_EDDSA, "eddsa"}, + {0, NULL}, +}; + +static const id_str_pair g10_curve_aliases[] = { + {PGP_CURVE_NIST_P_256, "NIST P-256"}, + {PGP_CURVE_NIST_P_256, "1.2.840.10045.3.1.7"}, + {PGP_CURVE_NIST_P_256, "prime256v1"}, + {PGP_CURVE_NIST_P_256, "secp256r1"}, + {PGP_CURVE_NIST_P_256, "nistp256"}, + {PGP_CURVE_NIST_P_384, "NIST P-384"}, + {PGP_CURVE_NIST_P_384, "secp384r1"}, + {PGP_CURVE_NIST_P_384, "1.3.132.0.34"}, + {PGP_CURVE_NIST_P_384, "nistp384"}, + {PGP_CURVE_NIST_P_521, "NIST P-521"}, + {PGP_CURVE_NIST_P_521, "secp521r1"}, + {PGP_CURVE_NIST_P_521, "1.3.132.0.35"}, + {PGP_CURVE_NIST_P_521, "nistp521"}, + {PGP_CURVE_25519, "Curve25519"}, + {PGP_CURVE_25519, "1.3.6.1.4.1.3029.1.5.1"}, + {PGP_CURVE_ED25519, "Ed25519"}, + {PGP_CURVE_ED25519, "1.3.6.1.4.1.11591.15.1"}, + {PGP_CURVE_BP256, "brainpoolP256r1"}, + {PGP_CURVE_BP256, "1.3.36.3.3.2.8.1.1.7"}, + {PGP_CURVE_BP384, "brainpoolP384r1"}, + {PGP_CURVE_BP384, "1.3.36.3.3.2.8.1.1.11"}, + {PGP_CURVE_BP512, "brainpoolP512r1"}, + {PGP_CURVE_BP512, "1.3.36.3.3.2.8.1.1.13"}, + {PGP_CURVE_P256K1, "secp256k1"}, + {PGP_CURVE_P256K1, "1.3.132.0.10"}, + {0, NULL}, +}; + +static const id_str_pair g10_curve_names[] = { + {PGP_CURVE_NIST_P_256, "NIST P-256"}, + {PGP_CURVE_NIST_P_384, "NIST P-384"}, + {PGP_CURVE_NIST_P_521, "NIST P-521"}, + {PGP_CURVE_ED25519, "Ed25519"}, + {PGP_CURVE_25519, "Curve25519"}, + {PGP_CURVE_BP256, "brainpoolP256r1"}, + {PGP_CURVE_BP384, "brainpoolP384r1"}, + {PGP_CURVE_BP512, "brainpoolP512r1"}, + {PGP_CURVE_P256K1, "secp256k1"}, + {0, NULL}, +}; + +static const format_info * +find_format(pgp_symm_alg_t cipher, pgp_cipher_mode_t mode, pgp_hash_alg_t hash_alg) +{ + for (size_t i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].cipher == cipher && formats[i].cipher_mode == mode && + formats[i].hash_alg == hash_alg) { + return &formats[i]; + } + } + return NULL; +} + +static const format_info * +parse_format(const char *format, size_t format_len) +{ + for (size_t i = 0; i < ARRAY_SIZE(formats); i++) { + if (strlen(formats[i].g10_type) == format_len && + !strncmp(formats[i].g10_type, format, format_len)) { + return &formats[i]; + } + } + return NULL; +} + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +void +gnupg_sexp_t::add(unsigned u) +{ + char s[sizeof(STR(UINT_MAX)) + 1]; + snprintf(s, sizeof(s), "%u", u); + push_back(std::make_shared<sexp_string_t>(s)); +} + +std::shared_ptr<gnupg_sexp_t> +gnupg_sexp_t::add_sub() +{ + auto res = std::make_shared<gnupg_sexp_t>(); + push_back(res); + return res; +} + +/* + * Parse S-expression + * https://people.csail.mit.edu/rivest/Sexp.txt + * sexp library supports canonical and advanced transport formats + * as well as base64 encoding of canonical + */ + +bool +gnupg_sexp_t::parse(const char *r_bytes, size_t r_length, size_t depth) +{ + bool res = false; + std::istringstream iss(std::string(r_bytes, r_length)); + try { + sexp_input_stream_t sis(&iss, depth); + sexp_list_t::parse(sis.set_byte_size(8)->get_char()); + res = true; + } catch (sexp_exception_t &e) { + RNP_LOG("%s", e.what()); + } + return res; +} + +/* + * Parse gnupg extended private key file ("G23") + * https://github.com/gpg/gnupg/blob/main/agent/keyformat.txt + */ + +bool +gnupg_extended_private_key_t::parse(const char *r_bytes, size_t r_length, size_t depth) +{ + bool res = false; + std::istringstream iss(std::string(r_bytes, r_length)); + try { + ext_key_input_stream_t g23_is(&iss, depth); + g23_is.scan(*this); + res = true; + } catch (sexp_exception_t &e) { + RNP_LOG("%s", e.what()); + } + return res; +} + +static const sexp_list_t * +lookup_var(const sexp_list_t *list, const std::string &name) noexcept +{ + const sexp_list_t *res = nullptr; + // We are looking for a list element (condition 1) + // that: + // -- has at least two SEXP elements (condition 2) + // -- has a SEXP string at 0 postion (condition 3) + // matching given name (condition 4) + auto match = [name](const std::shared_ptr<sexp_object_t> &ptr) { + bool r = false; + auto r1 = ptr->sexp_list_view(); + if (r1 && r1->size() >= 2) { // conditions (1) and (2) + auto r2 = r1->sexp_string_at(0); + if (r2 && r2 == name) // conditions (3) and (4) + r = true; + } + return r; + }; + auto r3 = std::find_if(list->begin(), list->end(), match); + if (r3 == list->end()) + RNP_LOG("Haven't got variable '%s'", name.c_str()); + else + res = (*r3)->sexp_list_view(); + return res; +} + +static const sexp_string_t * +lookup_var_data(const sexp_list_t *list, const std::string &name) noexcept +{ + const sexp_list_t *var = lookup_var(list, name); + if (!var) { + return NULL; + } + + if (!var->at(1)->is_sexp_string()) { + RNP_LOG("Expected block value"); + return NULL; + } + + return var->sexp_string_at(1); +} + +static bool +read_mpi(const sexp_list_t *list, const std::string &name, pgp_mpi_t &val) noexcept +{ + const sexp_string_t *data = lookup_var_data(list, name); + if (!data) { + return false; + } + + /* strip leading zero */ + const auto &bytes = data->get_string(); + if ((bytes.size() > 1) && !bytes[0] && (bytes[1] & 0x80)) { + return mem2mpi(&val, bytes.data() + 1, bytes.size() - 1); + } + return mem2mpi(&val, bytes.data(), bytes.size()); +} + +static bool +read_curve(const sexp_list_t *list, const std::string &name, pgp_ec_key_t &key) noexcept +{ + const sexp_string_t *data = lookup_var_data(list, name); + if (!data) { + return false; + } + + const auto &bytes = data->get_string(); + pgp_curve_t curve = static_cast<pgp_curve_t>( + id_str_pair::lookup(g10_curve_aliases, data->get_string(), PGP_CURVE_UNKNOWN)); + if (curve != PGP_CURVE_UNKNOWN) { + key.curve = curve; + return true; + } + RNP_LOG("Unknown curve: %.*s", (int) bytes.size(), (char *) bytes.data()); + return false; +} + +void +gnupg_sexp_t::add_mpi(const std::string &name, const pgp_mpi_t &mpi) +{ + auto sub_s_exp = add_sub(); + sub_s_exp->push_back(std::make_shared<sexp_string_t>(name)); + auto value_block = std::make_shared<sexp_string_t>(); + sub_s_exp->push_back(value_block); + + sexp_simple_string_t data; + size_t len = mpi_bytes(&mpi); + size_t idx; + + for (idx = 0; (idx < len) && !mpi.mpi[idx]; idx++) + ; + + if (idx < len) { + if (mpi.mpi[idx] & 0x80) { + data.append(0); + data.std::basic_string<uint8_t>::append(mpi.mpi + idx, len - idx); + } else { + data.assign(mpi.mpi + idx, mpi.mpi + len); + } + value_block->set_string(data); + } +} + +void +gnupg_sexp_t::add_curve(const std::string &name, const pgp_ec_key_t &key) +{ + const char *curve = id_str_pair::lookup(g10_curve_names, key.curve, NULL); + if (!curve) { + RNP_LOG("unknown curve"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + auto psub_s_exp = add_sub(); + psub_s_exp->add(name); + psub_s_exp->add(curve); + + if ((key.curve != PGP_CURVE_ED25519) && (key.curve != PGP_CURVE_25519)) { + return; + } + + psub_s_exp = add_sub(); + psub_s_exp->add("flags"); + psub_s_exp->add((key.curve == PGP_CURVE_ED25519) ? "eddsa" : "djb-tweak"); +} + +static bool +parse_pubkey(pgp_key_pkt_t &pubkey, const sexp_list_t *s_exp, pgp_pubkey_alg_t alg) +{ + pubkey.version = PGP_V4; + pubkey.alg = alg; + pubkey.material.alg = alg; + switch (alg) { + case PGP_PKA_DSA: + if (!read_mpi(s_exp, "p", pubkey.material.dsa.p) || + !read_mpi(s_exp, "q", pubkey.material.dsa.q) || + !read_mpi(s_exp, "g", pubkey.material.dsa.g) || + !read_mpi(s_exp, "y", pubkey.material.dsa.y)) { + return false; + } + break; + + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!read_mpi(s_exp, "n", pubkey.material.rsa.n) || + !read_mpi(s_exp, "e", pubkey.material.rsa.e)) { + return false; + } + break; + + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!read_mpi(s_exp, "p", pubkey.material.eg.p) || + !read_mpi(s_exp, "g", pubkey.material.eg.g) || + !read_mpi(s_exp, "y", pubkey.material.eg.y)) { + return false; + } + break; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: + if (!read_curve(s_exp, "curve", pubkey.material.ec) || + !read_mpi(s_exp, "q", pubkey.material.ec.p)) { + return false; + } + if (pubkey.material.ec.curve == PGP_CURVE_ED25519) { + /* need to adjust it here since 'ecc' key type defaults to ECDSA */ + pubkey.alg = PGP_PKA_EDDSA; + pubkey.material.alg = PGP_PKA_EDDSA; + } + break; + default: + RNP_LOG("Unsupported public key algorithm: %d", (int) alg); + return false; + } + + return true; +} + +static bool +parse_seckey(pgp_key_pkt_t &seckey, const sexp_list_t *s_exp, pgp_pubkey_alg_t alg) +{ + switch (alg) { + case PGP_PKA_DSA: + if (!read_mpi(s_exp, "x", seckey.material.dsa.x)) { + return false; + } + break; + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!read_mpi(s_exp, "d", seckey.material.rsa.d) || + !read_mpi(s_exp, "p", seckey.material.rsa.p) || + !read_mpi(s_exp, "q", seckey.material.rsa.q) || + !read_mpi(s_exp, "u", seckey.material.rsa.u)) { + return false; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!read_mpi(s_exp, "x", seckey.material.eg.x)) { + return false; + } + break; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: + if (!read_mpi(s_exp, "d", seckey.material.ec.x)) { + return false; + } + break; + default: + RNP_LOG("Unsupported public key algorithm: %d", (int) alg); + return false; + } + + seckey.material.secret = true; + return true; +} + +static bool +decrypt_protected_section(const sexp_simple_string_t &encrypted_data, + const pgp_key_pkt_t & seckey, + const std::string & password, + gnupg_sexp_t & r_s_exp, + uint8_t * associated_data, + size_t associated_data_len) +{ + const format_info * info = NULL; + unsigned keysize = 0; + uint8_t derived_key[PGP_MAX_KEY_SIZE]; + uint8_t * decrypted_data = NULL; + size_t decrypted_data_len = 0; + size_t output_written = 0; + size_t input_consumed = 0; + std::unique_ptr<Cipher> dec; + bool ret = false; + + const char *decrypted_bytes; + size_t s_exp_len; + + // sanity checks + const pgp_key_protection_t &prot = seckey.sec_protection; + keysize = pgp_key_size(prot.symm_alg); + if (!keysize) { + RNP_LOG("parse_seckey: unknown symmetric algo"); + goto done; + } + // find the protection format in our table + info = find_format(prot.symm_alg, prot.cipher_mode, prot.s2k.hash_alg); + if (!info) { + RNP_LOG("Unsupported format, alg: %d, chiper_mode: %d, hash: %d", + prot.symm_alg, + prot.cipher_mode, + prot.s2k.hash_alg); + goto done; + } + + // derive the key + if (pgp_s2k_iterated(prot.s2k.hash_alg, + derived_key, + keysize, + password.c_str(), + prot.s2k.salt, + prot.s2k.iterations)) { + RNP_LOG("pgp_s2k_iterated failed"); + goto done; + } + + // decrypt + decrypted_data = (uint8_t *) malloc(encrypted_data.size()); + if (decrypted_data == NULL) { + RNP_LOG("can't allocate memory"); + goto done; + } + dec = Cipher::decryption( + info->cipher, info->cipher_mode, info->tag_length, info->disable_padding); + if (!dec || !dec->set_key(derived_key, keysize)) { + goto done; + } + if (associated_data != nullptr && associated_data_len != 0) { + if (!dec->set_ad(associated_data, associated_data_len)) { + goto done; + } + } + // Nonce shall be the last chunk of associated data + if (!dec->set_iv(prot.iv, info->iv_size)) { + goto done; + } + if (!dec->finish(decrypted_data, + encrypted_data.size(), + &output_written, + encrypted_data.data(), + encrypted_data.size(), + &input_consumed)) { + goto done; + } + decrypted_data_len = output_written; + s_exp_len = decrypted_data_len; + decrypted_bytes = (const char *) decrypted_data; + + // parse and validate the decrypted s-exp + + if (!r_s_exp.parse(decrypted_bytes, s_exp_len, SXP_MAX_DEPTH)) { + goto done; + } + if (!r_s_exp.size() || r_s_exp.at(0)->is_sexp_string()) { + RNP_LOG("Hasn't got sub s-exp with key data."); + goto done; + } + ret = true; +done: + if (!ret) { + r_s_exp.clear(); + } + secure_clear(decrypted_data, decrypted_data_len); + free(decrypted_data); + return ret; +} + +static bool +parse_protected_seckey(pgp_key_pkt_t &seckey, const sexp_list_t *list, const char *password) +{ + // find and validate the protected section + const sexp_list_t *protected_key = lookup_var(list, "protected"); + if (!protected_key) { + RNP_LOG("missing protected section"); + return false; + } + if (protected_key->size() != 4 || !protected_key->at(1)->is_sexp_string() || + protected_key->at(2)->is_sexp_string() || !protected_key->at(3)->is_sexp_string()) { + RNP_LOG("Wrong protected format, expected: (protected mode (params) " + "encrypted_octet_string)\n"); + return false; + } + + // lookup the protection format + auto & fmt_bt = protected_key->sexp_string_at(1)->get_string(); + const format_info *format = parse_format((const char *) fmt_bt.data(), fmt_bt.size()); + if (!format) { + RNP_LOG("Unsupported protected mode: '%.*s'\n", + (int) fmt_bt.size(), + (const char *) fmt_bt.data()); + return false; + } + + // fill in some fields based on the lookup above + pgp_key_protection_t &prot = seckey.sec_protection; + prot.symm_alg = format->cipher; + prot.cipher_mode = format->cipher_mode; + prot.s2k.hash_alg = format->hash_alg; + + // locate and validate the protection parameters + auto params = protected_key->sexp_list_at(2); + if (params->size() != 2 || params->at(0)->is_sexp_string() || + !params->at(1)->is_sexp_string()) { + RNP_LOG("Wrong params format, expected: ((hash salt no_of_iterations) iv)\n"); + return false; + } + + // locate and validate the (hash salt no_of_iterations) exp + auto alg = params->sexp_list_at(0); + if (alg->size() != 3 || !alg->at(0)->is_sexp_string() || !alg->at(1)->is_sexp_string() || + !alg->at(2)->is_sexp_string()) { + RNP_LOG("Wrong params sub-level format, expected: (hash salt no_of_iterations)\n"); + return false; + } + auto &hash_bt = alg->sexp_string_at(0)->get_string(); + if (hash_bt != "sha1") { + RNP_LOG("Wrong hashing algorithm, should be sha1 but %.*s\n", + (int) hash_bt.size(), + (const char *) hash_bt.data()); + return false; + } + + // fill in some constant values + prot.s2k.hash_alg = PGP_HASH_SHA1; + prot.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED; + prot.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED; + + // check salt size + auto &salt_bt = alg->sexp_string_at(1)->get_string(); + if (salt_bt.size() != PGP_SALT_SIZE) { + RNP_LOG("Wrong salt size, should be %d but %d\n", PGP_SALT_SIZE, (int) salt_bt.size()); + return false; + } + + // salt + memcpy(prot.s2k.salt, salt_bt.data(), salt_bt.size()); + // s2k iterations + auto iter = alg->sexp_string_at(2); + prot.s2k.iterations = iter->as_unsigned(); + if (prot.s2k.iterations == UINT_MAX) { + RNP_LOG("Wrong numbers of iteration, %.*s\n", + (int) iter->get_string().size(), + (const char *) iter->get_string().data()); + return false; + } + + // iv + auto &iv_bt = params->sexp_string_at(1)->get_string(); + if (iv_bt.size() != format->iv_size) { + RNP_LOG("Wrong nonce size, should be %zu but %zu\n", format->iv_size, iv_bt.size()); + return false; + } + memcpy(prot.iv, iv_bt.data(), iv_bt.size()); + + // we're all done if no password was provided (decryption not requested) + if (!password) { + seckey.material.secret = false; + return true; + } + + // password was provided, so decrypt + auto & enc_bt = protected_key->sexp_string_at(3)->get_string(); + gnupg_sexp_t decrypted_s_exp; + + // Build associated data (AD) that is not included in the ciphertext but that should be + // authenticated. gnupg builds AD as follows (file 'protect.c' do_encryption/do_decryption + // functions) + // -- "protected-private-key" section content + // -- less "protected" subsection + // -- serialized in canonical format + std::string associated_data; + if (format->with_associated_data) { + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + os.var_put_char('('); + for_each(list->begin(), list->end(), [&](const std::shared_ptr<sexp_object_t> &obj) { + if (obj->sexp_list_view() != protected_key) + obj->print_canonical(&os); + }); + os.var_put_char(')'); + associated_data = oss.str(); + } + + if (!decrypt_protected_section( + enc_bt, + seckey, + password, + decrypted_s_exp, + format->with_associated_data ? (uint8_t *) associated_data.data() : nullptr, + format->with_associated_data ? associated_data.length() : 0)) { + return false; + } + // see if we have a protected-at section + char protected_at[G10_PROTECTED_AT_SIZE] = {0}; + auto protected_at_data = lookup_var_data(list, "protected-at"); + if (protected_at_data) { + if (protected_at_data->get_string().size() != G10_PROTECTED_AT_SIZE) { + RNP_LOG("protected-at has wrong length: %zu, expected, %d\n", + protected_at_data->get_string().size(), + G10_PROTECTED_AT_SIZE); + return false; + } + memcpy(protected_at, + protected_at_data->get_string().data(), + protected_at_data->get_string().size()); + } + // parse MPIs + if (!parse_seckey(seckey, decrypted_s_exp.sexp_list_at(0), seckey.alg)) { + RNP_LOG("failed to parse seckey"); + return false; + } + // check hash, if present + if (decrypted_s_exp.size() > 1) { + if (decrypted_s_exp.at(1)->is_sexp_string()) { + RNP_LOG("Wrong hash block type."); + return false; + } + auto sub_el = decrypted_s_exp.sexp_list_at(1); + if (sub_el->size() < 3 || !sub_el->at(0)->is_sexp_string() || + !sub_el->at(1)->is_sexp_string() || !sub_el->at(2)->is_sexp_string()) { + RNP_LOG("Wrong hash block structure."); + return false; + } + + auto &hkey = sub_el->sexp_string_at(0)->get_string(); + if (hkey != "hash") { + RNP_LOG("Has got wrong hash block at encrypted key data."); + return false; + } + auto &halg = sub_el->sexp_string_at(1)->get_string(); + if (halg != "sha1") { + RNP_LOG("Supported only sha1 hash at encrypted private key."); + return false; + } + uint8_t checkhash[G10_SHA1_HASH_SIZE]; + if (!g10_calculated_hash(seckey, protected_at, checkhash)) { + RNP_LOG("failed to calculate hash"); + return false; + } + auto &hval = sub_el->sexp_string_at(2)->get_string(); + if (hval.size() != G10_SHA1_HASH_SIZE || + memcmp(checkhash, hval.data(), G10_SHA1_HASH_SIZE)) { + RNP_LOG("Incorrect hash at encrypted private key."); + return false; + } + } + seckey.material.secret = true; + return true; +} + +static bool +g23_parse_seckey(pgp_key_pkt_t &seckey, + const uint8_t *data, + size_t data_len, + const char * password) +{ + gnupg_extended_private_key_t g23_extended_key; + + const char *bytes = (const char *) data; + if (!g23_extended_key.parse(bytes, data_len, SXP_MAX_DEPTH)) { + RNP_LOG("Failed to parse s-exp."); + return false; + } + // Although the library parses full g23 extended key + // we extract and use g10 part only + const sexp_list_t &g10_key = g23_extended_key.key; + + /* expected format: + * (<type> + * (<algo> + * (x <mpi>) + * (y <mpi>) + * ) + * ) + */ + + if (g10_key.size() != 2 || !g10_key.at(0)->is_sexp_string() || + !g10_key.at(1)->is_sexp_list()) { + RNP_LOG("Wrong format, expected: (<type> (...))"); + return false; + } + + bool is_protected = false; + + auto &name = g10_key.sexp_string_at(0)->get_string(); + if (name == "private-key") { + is_protected = false; + } else if (name == "protected-private-key") { + is_protected = true; + } else { + RNP_LOG("Unsupported top-level block: '%.*s'", + (int) name.size(), + (const char *) name.data()); + return false; + } + + auto alg_s_exp = g10_key.sexp_list_at(1); + if (alg_s_exp->size() < 2) { + RNP_LOG("Wrong count of algorithm-level elements: %zu", alg_s_exp->size()); + return false; + } + + if (!alg_s_exp->at(0)->is_sexp_string()) { + RNP_LOG("Expected block with algorithm name, but has s-exp"); + return false; + } + + auto & alg_bt = alg_s_exp->sexp_string_at(0)->get_string(); + pgp_pubkey_alg_t alg = static_cast<pgp_pubkey_alg_t>( + id_str_pair::lookup(g10_alg_aliases, alg_bt.c_str(), PGP_PKA_NOTHING)); + if (alg == PGP_PKA_NOTHING) { + RNP_LOG( + "Unsupported algorithm: '%.*s'", (int) alg_bt.size(), (const char *) alg_bt.data()); + return false; + } + + bool ret = false; + if (!parse_pubkey(seckey, alg_s_exp, alg)) { + RNP_LOG("failed to parse pubkey"); + goto done; + } + + if (is_protected) { + if (!parse_protected_seckey(seckey, alg_s_exp, password)) { + goto done; + } + } else { + seckey.sec_protection.s2k.usage = PGP_S2KU_NONE; + seckey.sec_protection.symm_alg = PGP_SA_PLAINTEXT; + seckey.sec_protection.s2k.hash_alg = PGP_HASH_UNKNOWN; + if (!parse_seckey(seckey, alg_s_exp, alg)) { + RNP_LOG("failed to parse seckey"); + goto done; + } + } + ret = true; +done: + if (!ret) { + seckey = pgp_key_pkt_t(); + } + return ret; +} + +pgp_key_pkt_t * +g10_decrypt_seckey(const pgp_rawpacket_t &raw, + const pgp_key_pkt_t & pubkey, + const char * password) +{ + if (!password) { + return NULL; + } + auto seckey = std::unique_ptr<pgp_key_pkt_t>(new pgp_key_pkt_t(pubkey, false)); + if (!g23_parse_seckey(*seckey, raw.raw.data(), raw.raw.size(), password)) { + return NULL; + } + /* g10 has the same 'ecc' algo for ECDSA/ECDH/EDDSA. Probably should be better place to fix + * this. */ + seckey->alg = pubkey.alg; + seckey->material.alg = pubkey.material.alg; + return seckey.release(); +} + +static bool +copy_secret_fields(pgp_key_pkt_t &dst, const pgp_key_pkt_t &src) +{ + switch (src.alg) { + case PGP_PKA_DSA: + dst.material.dsa.x = src.material.dsa.x; + break; + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + dst.material.rsa.d = src.material.rsa.d; + dst.material.rsa.p = src.material.rsa.p; + dst.material.rsa.q = src.material.rsa.q; + dst.material.rsa.u = src.material.rsa.u; + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + dst.material.eg.x = src.material.eg.x; + break; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: + dst.material.ec.x = src.material.ec.x; + break; + default: + RNP_LOG("Unsupported public key algorithm: %d", (int) src.alg); + return false; + } + + dst.material.secret = src.material.secret; + dst.sec_protection = src.sec_protection; + dst.tag = is_subkey_pkt(dst.tag) ? PGP_PKT_SECRET_SUBKEY : PGP_PKT_SECRET_KEY; + return true; +} + +bool +rnp_key_store_g10_from_src(rnp_key_store_t * key_store, + pgp_source_t * src, + const pgp_key_provider_t *key_provider) +{ + try { + /* read src to the memory */ + rnp::MemorySource memsrc(*src); + /* parse secret key: fills material and sec_protection only */ + pgp_key_pkt_t seckey; + if (!g23_parse_seckey(seckey, (uint8_t *) memsrc.memory(), memsrc.size(), NULL)) { + return false; + } + /* copy public key fields if any */ + pgp_key_t key; + if (key_provider) { + pgp_key_request_ctx_t req_ctx(PGP_OP_MERGE_INFO, false, PGP_KEY_SEARCH_GRIP); + if (!rnp_key_store_get_key_grip(&seckey.material, req_ctx.search.by.grip)) { + return false; + } + + const pgp_key_t *pubkey = pgp_request_key(key_provider, &req_ctx); + if (!pubkey) { + return false; + } + + /* public key packet has some more info then the secret part */ + key = pgp_key_t(*pubkey, true); + if (!copy_secret_fields(key.pkt(), seckey)) { + return false; + } + } else { + key.set_pkt(std::move(seckey)); + } + /* set rawpkt */ + key.set_rawpkt( + pgp_rawpacket_t((uint8_t *) memsrc.memory(), memsrc.size(), PGP_PKT_RESERVED)); + key.format = PGP_KEY_STORE_G10; + if (!rnp_key_store_add_key(key_store, &key)) { + return false; + } + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +/* + * Write G10 S-exp to buffer + * + * Supported format: (1:a2:ab(3:asd1:a)) + */ +bool +gnupg_sexp_t::write(pgp_dest_t &dst) const noexcept +{ + bool res = false; + try { + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + print_canonical(&os); + const std::string &s = oss.str(); + const char * ss = s.c_str(); + dst_write(&dst, ss, s.size()); + res = (dst.werr == RNP_SUCCESS); + + } catch (...) { + } + + return res; +} + +void +gnupg_sexp_t::add_pubkey(const pgp_key_pkt_t &key) +{ + switch (key.alg) { + case PGP_PKA_DSA: + add("dsa"); + add_mpi("p", key.material.dsa.p); + add_mpi("q", key.material.dsa.q); + add_mpi("g", key.material.dsa.g); + add_mpi("y", key.material.dsa.y); + break; + case PGP_PKA_RSA_SIGN_ONLY: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA: + add("rsa"); + add_mpi("n", key.material.rsa.n); + add_mpi("e", key.material.rsa.e); + break; + case PGP_PKA_ELGAMAL: + add("elg"); + add_mpi("p", key.material.eg.p); + add_mpi("g", key.material.eg.g); + add_mpi("y", key.material.eg.y); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: + add("ecc"); + add_curve("curve", key.material.ec); + add_mpi("q", key.material.ec.p); + break; + default: + RNP_LOG("Unsupported public key algorithm: %d", (int) key.alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +void +gnupg_sexp_t::add_seckey(const pgp_key_pkt_t &key) +{ + switch (key.alg) { + case PGP_PKA_DSA: + add_mpi("x", key.material.dsa.x); + break; + case PGP_PKA_RSA_SIGN_ONLY: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA: + add_mpi("d", key.material.rsa.d); + add_mpi("p", key.material.rsa.p); + add_mpi("q", key.material.rsa.q); + add_mpi("u", key.material.rsa.u); + break; + case PGP_PKA_ELGAMAL: + add_mpi("x", key.material.eg.x); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: { + add_mpi("d", key.material.ec.x); + break; + } + default: + RNP_LOG("Unsupported public key algorithm: %d", (int) key.alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +rnp::secure_vector<uint8_t> +gnupg_sexp_t::write_padded(size_t padblock) const +{ + rnp::MemoryDest raw; + raw.set_secure(true); + + if (!write(raw.dst())) { + RNP_LOG("failed to serialize s_exp"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + // add padding! + size_t padding = padblock - raw.writeb() % padblock; + for (size_t i = 0; i < padding; i++) { + raw.write("X", 1); + } + if (raw.werr()) { + RNP_LOG("failed to write padding"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + const uint8_t *mem = (uint8_t *) raw.memory(); + return rnp::secure_vector<uint8_t>(mem, mem + raw.writeb()); +} + +void +gnupg_sexp_t::add_protected_seckey(pgp_key_pkt_t & seckey, + const std::string & password, + rnp::SecurityContext &ctx) +{ + pgp_key_protection_t &prot = seckey.sec_protection; + if (prot.s2k.specifier != PGP_S2KS_ITERATED_AND_SALTED) { + RNP_LOG("Bad s2k specifier: %d", (int) prot.s2k.specifier); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + const format_info *format = + find_format(prot.symm_alg, prot.cipher_mode, prot.s2k.hash_alg); + if (!format) { + RNP_LOG("Unknown protection format."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + // randomize IV and salt + ctx.rng.get(prot.iv, sizeof(prot.iv)); + ctx.rng.get(prot.s2k.salt, sizeof(prot.s2k.salt)); + + // write seckey + gnupg_sexp_t raw_s_exp; + auto psub_s_exp = raw_s_exp.add_sub(); + psub_s_exp->add_seckey(seckey); + + // calculate hash + char protected_at[G10_PROTECTED_AT_SIZE + 1]; + uint8_t checksum[G10_SHA1_HASH_SIZE]; + // TODO: how critical is it if we have a skewed timestamp here due to y2k38 problem? + struct tm tm = {}; + rnp_gmtime(ctx.time(), tm); + strftime(protected_at, sizeof(protected_at), "%Y%m%dT%H%M%S", &tm); + if (!g10_calculated_hash(seckey, protected_at, checksum)) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + psub_s_exp = raw_s_exp.add_sub(); + psub_s_exp->add("hash"); + psub_s_exp->add("sha1"); + psub_s_exp->add(checksum, sizeof(checksum)); + + /* write raw secret key to the memory */ + rnp::secure_vector<uint8_t> rawkey = raw_s_exp.write_padded(format->cipher_block_size); + + /* derive encrypting key */ + unsigned keysize = pgp_key_size(prot.symm_alg); + if (!keysize) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> derived_key; + if (pgp_s2k_iterated(format->hash_alg, + derived_key.data(), + keysize, + password.c_str(), + prot.s2k.salt, + prot.s2k.iterations)) { + RNP_LOG("s2k key derivation failed"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* encrypt raw key */ + std::unique_ptr<Cipher> enc( + Cipher::encryption(format->cipher, format->cipher_mode, 0, true)); + if (!enc || !enc->set_key(derived_key.data(), keysize) || + !enc->set_iv(prot.iv, format->iv_size)) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + size_t output_written, input_consumed; + std::vector<uint8_t> enckey(rawkey.size()); + + if (!enc->finish(enckey.data(), + enckey.size(), + &output_written, + rawkey.data(), + rawkey.size(), + &input_consumed)) { + RNP_LOG("Encryption failed"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* build s_exp with encrypted key */ + psub_s_exp = add_sub(); + psub_s_exp->add("protected"); + psub_s_exp->add(format->g10_type); + /* protection params: s2k, iv */ + auto psub_sub_s_exp = psub_s_exp->add_sub(); + /* s2k params: hash, salt, iterations */ + auto psub_sub_sub_s_exp = psub_sub_s_exp->add_sub(); + psub_sub_sub_s_exp->add("sha1"); + psub_sub_sub_s_exp->add(prot.s2k.salt, PGP_SALT_SIZE); + psub_sub_sub_s_exp->add(prot.s2k.iterations); + psub_sub_s_exp->add(prot.iv, format->iv_size); + /* encrypted key data itself */ + psub_s_exp->add(enckey.data(), enckey.size()); + /* protected-at */ + psub_s_exp = add_sub(); + psub_s_exp->add("protected-at"); + psub_s_exp->add((uint8_t *) protected_at, G10_PROTECTED_AT_SIZE); +} + +bool +g10_write_seckey(pgp_dest_t * dst, + pgp_key_pkt_t * seckey, + const char * password, + rnp::SecurityContext &ctx) +{ + bool is_protected = true; + + switch (seckey->sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + is_protected = false; + break; + case PGP_S2KU_ENCRYPTED_AND_HASHED: + is_protected = true; + // TODO: these are forced for now, until openpgp-native is implemented + seckey->sec_protection.symm_alg = PGP_SA_AES_128; + seckey->sec_protection.cipher_mode = PGP_CIPHER_MODE_CBC; + seckey->sec_protection.s2k.hash_alg = PGP_HASH_SHA1; + break; + default: + RNP_LOG("unsupported s2k usage"); + return false; + } + + try { + gnupg_sexp_t s_exp; + s_exp.add(is_protected ? "protected-private-key" : "private-key"); + auto pkey = s_exp.add_sub(); + pkey->add_pubkey(*seckey); + + if (is_protected) { + pkey->add_protected_seckey(*seckey, password, ctx); + } else { + pkey->add_seckey(*seckey); + } + return s_exp.write(*dst) && !dst->werr; + } catch (const std::exception &e) { + RNP_LOG("Failed to write g10 key: %s", e.what()); + return false; + } +} + +static bool +g10_calculated_hash(const pgp_key_pkt_t &key, const char *protected_at, uint8_t *checksum) +{ + try { + /* populate s_exp */ + gnupg_sexp_t s_exp; + s_exp.add_pubkey(key); + s_exp.add_seckey(key); + auto s_sub_exp = s_exp.add_sub(); + s_sub_exp->add("protected-at"); + s_sub_exp->add((uint8_t *) protected_at, G10_PROTECTED_AT_SIZE); + /* write it to memdst */ + rnp::MemoryDest memdst; + memdst.set_secure(true); + if (!s_exp.write(memdst.dst())) { + RNP_LOG("Failed to write s_exp"); + return false; + } + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + hash->add(memdst.memory(), memdst.writeb()); + hash->finish(checksum); + return true; + } catch (const std::exception &e) { + RNP_LOG("Failed to build s_exp: %s", e.what()); + return false; + } +} + +bool +rnp_key_store_gnupg_sexp_to_dst(pgp_key_t *key, pgp_dest_t *dest) +{ + if (key->format != PGP_KEY_STORE_G10) { + RNP_LOG("incorrect format: %d", key->format); + return false; + } + pgp_rawpacket_t &packet = key->rawpkt(); + dst_write(dest, packet.raw.data(), packet.raw.size()); + return dest->werr == RNP_SUCCESS; +} diff --git a/src/librekey/key_store_g10.h b/src/librekey/key_store_g10.h new file mode 100644 index 0000000..f770628 --- /dev/null +++ b/src/librekey/key_store_g10.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE RIBOSE, INC. AND CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_KEY_STORE_G10_H +#define RNP_KEY_STORE_G10_H + +#include <rekey/rnp_key_store.h> + +bool rnp_key_store_g10_from_src(rnp_key_store_t *, pgp_source_t *, const pgp_key_provider_t *); +bool rnp_key_store_gnupg_sexp_to_dst(pgp_key_t *, pgp_dest_t *); +bool g10_write_seckey(pgp_dest_t * dst, + pgp_key_pkt_t * seckey, + const char * password, + rnp::SecurityContext &ctx); +pgp_key_pkt_t *g10_decrypt_seckey(const pgp_rawpacket_t &raw, + const pgp_key_pkt_t & pubkey, + const char * password); + +#endif // RNP_KEY_STORE_G10_H diff --git a/src/librekey/key_store_kbx.cpp b/src/librekey/key_store_kbx.cpp new file mode 100644 index 0000000..bc504f6 --- /dev/null +++ b/src/librekey/key_store_kbx.cpp @@ -0,0 +1,706 @@ +/* + * Copyright (c) 2017-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/types.h> +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include <stdint.h> +#include <time.h> +#include <inttypes.h> +#include <cassert> + +#include "key_store_pgp.h" +#include "key_store_kbx.h" +#include "pgp-key.h" +#include <librepgp/stream-sig.h> + +/* same limit with GnuPG 2.1 */ +#define BLOB_SIZE_LIMIT (5 * 1024 * 1024) +/* limit the number of keys/sigs/uids in the blob */ +#define BLOB_OBJ_LIMIT 0x8000 + +#define BLOB_HEADER_SIZE 0x5 +#define BLOB_FIRST_SIZE 0x20 +#define BLOB_KEY_SIZE 0x1C +#define BLOB_UID_SIZE 0x0C +#define BLOB_SIG_SIZE 0x04 +#define BLOB_VALIDITY_SIZE 0x10 + +uint8_t +kbx_blob_t::ru8(size_t idx) +{ + return image_[idx]; +} + +uint16_t +kbx_blob_t::ru16(size_t idx) +{ + return read_uint16(image_.data() + idx); +} + +uint32_t +kbx_blob_t::ru32(size_t idx) +{ + return read_uint32(image_.data() + idx); +} + +kbx_blob_t::kbx_blob_t(std::vector<uint8_t> &data) +{ + if (data.size() < BLOB_HEADER_SIZE) { + RNP_LOG("Too small KBX blob."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + uint32_t len = read_uint32(data.data()); + if (len > BLOB_SIZE_LIMIT) { + RNP_LOG("Too large KBX blob."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (len != data.size()) { + RNP_LOG("KBX blob size mismatch."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + image_ = data; + type_ = (kbx_blob_type_t) ru8(4); +} + +bool +kbx_header_blob_t::parse() +{ + if (length() != BLOB_FIRST_SIZE) { + RNP_LOG("The first blob has wrong length: %" PRIu32 " but expected %d", + length(), + (int) BLOB_FIRST_SIZE); + return false; + } + + size_t idx = BLOB_HEADER_SIZE; + version_ = ru8(idx++); + if (version_ != 1) { + RNP_LOG("Wrong version, expect 1 but has %" PRIu8, version_); + return false; + } + + flags_ = ru16(idx); + idx += 2; + + // blob should contains a magic KBXf + if (memcmp(image_.data() + idx, "KBXf", 4)) { + RNP_LOG("The first blob hasn't got a KBXf magic string"); + return false; + } + idx += 4; + // RFU + idx += 4; + // File creation time + file_created_at_ = ru32(idx); + idx += 4; + // Duplicated? + file_created_at_ = ru32(idx); + // RFU +4 bytes + // RFU +4 bytes + return true; +} + +bool +kbx_pgp_blob_t::parse() +{ + if (image_.size() < 15 + BLOB_HEADER_SIZE) { + RNP_LOG("Too few data in the blob."); + return false; + } + + size_t idx = BLOB_HEADER_SIZE; + /* version */ + version_ = ru8(idx++); + if (version_ != 1) { + RNP_LOG("Wrong version: %" PRIu8, version_); + return false; + } + /* flags */ + flags_ = ru16(idx); + idx += 2; + /* keyblock offset */ + keyblock_offset_ = ru32(idx); + idx += 4; + /* keyblock length */ + keyblock_length_ = ru32(idx); + idx += 4; + + if ((keyblock_offset_ > image_.size()) || + (keyblock_offset_ > (UINT32_MAX - keyblock_length_)) || + (image_.size() < (keyblock_offset_ + keyblock_length_))) { + RNP_LOG("Wrong keyblock offset/length, blob size: %zu" + ", keyblock offset: %" PRIu32 ", length: %" PRIu32, + image_.size(), + keyblock_offset_, + keyblock_length_); + return false; + } + /* number of key blocks */ + size_t nkeys = ru16(idx); + idx += 2; + if (nkeys < 1) { + RNP_LOG("PGP blob should contains at least 1 key"); + return false; + } + if (nkeys > BLOB_OBJ_LIMIT) { + RNP_LOG("Too many keys in the PGP blob"); + return false; + } + + /* Size of the single key record */ + size_t keys_len = ru16(idx); + idx += 2; + if (keys_len < BLOB_KEY_SIZE) { + RNP_LOG( + "PGP blob needs %d bytes, but contains: %zu bytes", (int) BLOB_KEY_SIZE, keys_len); + return false; + } + + for (size_t i = 0; i < nkeys; i++) { + if (image_.size() - idx < keys_len) { + RNP_LOG("Too few bytes left for key blob"); + return false; + } + + kbx_pgp_key_t nkey = {}; + /* copy fingerprint */ + memcpy(nkey.fp, &image_[idx], 20); + idx += 20; + /* keyid offset */ + nkey.keyid_offset = ru32(idx); + idx += 4; + /* flags */ + nkey.flags = ru16(idx); + idx += 2; + /* RFU */ + idx += 2; + /* skip padding bytes if it existed */ + idx += keys_len - BLOB_KEY_SIZE; + keys_.push_back(std::move(nkey)); + } + + if (image_.size() - idx < 2) { + RNP_LOG("No data for sn_size"); + return false; + } + size_t sn_size = ru16(idx); + idx += 2; + + if (image_.size() - idx < sn_size) { + RNP_LOG("SN is %zu, while bytes left are %zu", sn_size, image_.size() - idx); + return false; + } + + if (sn_size) { + sn_ = {image_.begin() + idx, image_.begin() + idx + sn_size}; + idx += sn_size; + } + + if (image_.size() - idx < 4) { + RNP_LOG("Too few data for uids"); + return false; + } + size_t nuids = ru16(idx); + if (nuids > BLOB_OBJ_LIMIT) { + RNP_LOG("Too many uids in the PGP blob"); + return false; + } + + size_t uids_len = ru16(idx + 2); + idx += 4; + + if (uids_len < BLOB_UID_SIZE) { + RNP_LOG("Too few bytes for uid struct: %zu", uids_len); + return false; + } + + for (size_t i = 0; i < nuids; i++) { + if (image_.size() - idx < uids_len) { + RNP_LOG("Too few bytes to read uid struct."); + return false; + } + kbx_pgp_uid_t nuid = {}; + /* offset */ + nuid.offset = ru32(idx); + idx += 4; + /* length */ + nuid.length = ru32(idx); + idx += 4; + /* flags */ + nuid.flags = ru16(idx); + idx += 2; + /* validity */ + nuid.validity = ru8(idx); + idx++; + /* RFU */ + idx++; + // skip padding bytes if it existed + idx += uids_len - BLOB_UID_SIZE; + + uids_.push_back(std::move(nuid)); + } + + if (image_.size() - idx < 4) { + RNP_LOG("No data left for sigs"); + return false; + } + + size_t nsigs = ru16(idx); + if (nsigs > BLOB_OBJ_LIMIT) { + RNP_LOG("Too many sigs in the PGP blob"); + return false; + } + + size_t sigs_len = ru16(idx + 2); + idx += 4; + + if (sigs_len < BLOB_SIG_SIZE) { + RNP_LOG("Too small SIGN structure: %zu", uids_len); + return false; + } + + for (size_t i = 0; i < nsigs; i++) { + if (image_.size() - idx < sigs_len) { + RNP_LOG("Too few data for sig"); + return false; + } + + kbx_pgp_sig_t nsig = {}; + nsig.expired = ru32(idx); + idx += 4; + + // skip padding bytes if it existed + idx += (sigs_len - BLOB_SIG_SIZE); + + sigs_.push_back(nsig); + } + + if (image_.size() - idx < BLOB_VALIDITY_SIZE) { + RNP_LOG("Too few data for trust/validities"); + return false; + } + + ownertrust_ = ru8(idx); + idx++; + all_validity_ = ru8(idx); + idx++; + // RFU + idx += 2; + recheck_after_ = ru32(idx); + idx += 4; + latest_timestamp_ = ru32(idx); + idx += 4; + blob_created_at_ = ru32(idx); + // do not forget to idx += 4 on further expansion + + // here starts keyblock, UID and reserved space for future usage + + // Maybe we should add checksum verify but GnuPG never checked it + // Checksum is last 20 bytes of blob and it is SHA-1, if it invalid MD5 and starts from 4 + // zero it is MD5. + + return true; +} + +static std::unique_ptr<kbx_blob_t> +rnp_key_store_kbx_parse_blob(const uint8_t *image, size_t image_len) +{ + std::unique_ptr<kbx_blob_t> blob; + // a blob shouldn't be less of length + type + if (image_len < BLOB_HEADER_SIZE) { + RNP_LOG("Blob size is %zu but it shouldn't be less of header", image_len); + return blob; + } + + try { + std::vector<uint8_t> data(image, image + image_len); + kbx_blob_type_t type = (kbx_blob_type_t) image[4]; + + switch (type) { + case KBX_EMPTY_BLOB: + blob = std::unique_ptr<kbx_blob_t>(new kbx_blob_t(data)); + break; + case KBX_HEADER_BLOB: + blob = std::unique_ptr<kbx_blob_t>(new kbx_header_blob_t(data)); + break; + case KBX_PGP_BLOB: + blob = std::unique_ptr<kbx_blob_t>(new kbx_pgp_blob_t(data)); + break; + case KBX_X509_BLOB: + // current we doesn't parse X509 blob, so, keep it as is + blob = std::unique_ptr<kbx_blob_t>(new kbx_blob_t(data)); + break; + // unsupported blob type + default: + RNP_LOG("Unsupported blob type: %d", (int) type); + return blob; + } + + if (!blob->parse()) { + return NULL; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return NULL; + } + return blob; +} + +bool +rnp_key_store_kbx_from_src(rnp_key_store_t * key_store, + pgp_source_t * src, + const pgp_key_provider_t *key_provider) +{ + try { + rnp::MemorySource mem(*src); + size_t has_bytes = mem.size(); + uint8_t * buf = (uint8_t *) mem.memory(); + + while (has_bytes > 4) { + size_t blob_length = read_uint32(buf); + if (blob_length > BLOB_SIZE_LIMIT) { + RNP_LOG("Blob size is %zu bytes but limit is %d bytes", + blob_length, + (int) BLOB_SIZE_LIMIT); + return false; + } + if (blob_length < BLOB_HEADER_SIZE) { + RNP_LOG("Too small blob header size"); + return false; + } + if (has_bytes < blob_length) { + RNP_LOG("Blob have size %zu bytes but file contains only %zu bytes", + blob_length, + has_bytes); + return false; + } + auto blob = rnp_key_store_kbx_parse_blob(buf, blob_length); + if (!blob.get()) { + RNP_LOG("Failed to parse blob"); + return false; + } + kbx_blob_t *pblob = blob.get(); + key_store->blobs.push_back(std::move(blob)); + + if (pblob->type() == KBX_PGP_BLOB) { + // parse keyblock if it existed + kbx_pgp_blob_t &pgp_blob = dynamic_cast<kbx_pgp_blob_t &>(*pblob); + if (!pgp_blob.keyblock_length()) { + RNP_LOG("PGP blob have zero size"); + return false; + } + + rnp::MemorySource blsrc(pgp_blob.image().data() + pgp_blob.keyblock_offset(), + pgp_blob.keyblock_length(), + false); + if (rnp_key_store_pgp_read_from_src(key_store, &blsrc.src())) { + return false; + } + } + + has_bytes -= blob_length; + buf += blob_length; + } + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +static bool +pbuf(pgp_dest_t *dst, const void *buf, size_t len) +{ + dst_write(dst, buf, len); + return dst->werr == RNP_SUCCESS; +} + +static bool +pu8(pgp_dest_t *dst, uint8_t p) +{ + return pbuf(dst, &p, 1); +} + +static bool +pu16(pgp_dest_t *dst, uint16_t f) +{ + uint8_t p[2]; + p[0] = (uint8_t)(f >> 8); + p[1] = (uint8_t) f; + return pbuf(dst, p, 2); +} + +static bool +pu32(pgp_dest_t *dst, uint32_t f) +{ + uint8_t p[4]; + STORE32BE(p, f); + return pbuf(dst, p, 4); +} + +static bool +rnp_key_store_kbx_write_header(rnp_key_store_t *key_store, pgp_dest_t *dst) +{ + uint16_t flags = 0; + uint32_t file_created_at = key_store->secctx.time(); + + if (!key_store->blobs.empty() && (key_store->blobs[0]->type() == KBX_HEADER_BLOB)) { + kbx_header_blob_t &blob = dynamic_cast<kbx_header_blob_t &>(*key_store->blobs[0]); + file_created_at = blob.file_created_at(); + } + + return !(!pu32(dst, BLOB_FIRST_SIZE) || !pu8(dst, KBX_HEADER_BLOB) || + !pu8(dst, 1) // version + || !pu16(dst, flags) || !pbuf(dst, "KBXf", 4) || !pu32(dst, 0) // RFU + || !pu32(dst, 0) // RFU + || !pu32(dst, file_created_at) || !pu32(dst, key_store->secctx.time()) || + !pu32(dst, 0)); // RFU +} + +static bool +rnp_key_store_kbx_write_pgp(rnp_key_store_t *key_store, pgp_key_t *key, pgp_dest_t *dst) +{ + rnp::MemoryDest mem(NULL, BLOB_SIZE_LIMIT); + + if (!pu32(&mem.dst(), 0)) { // length, we don't know length of blob yet, so it's 0 + return false; + } + + if (!pu8(&mem.dst(), KBX_PGP_BLOB) || !pu8(&mem.dst(), 1)) { // type, version + return false; + } + + if (!pu16(&mem.dst(), 0)) { // flags, not used by GnuPG + return false; + } + + if (!pu32(&mem.dst(), 0) || + !pu32(&mem.dst(), 0)) { // offset and length of keyblock, update later + return false; + } + + if (!pu16(&mem.dst(), 1 + key->subkey_count())) { // number of keys in keyblock + return false; + } + if (!pu16(&mem.dst(), 28)) { // size of key info structure) + return false; + } + + if (!pbuf(&mem.dst(), key->fp().fingerprint, PGP_FINGERPRINT_SIZE) || + !pu32(&mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4) + !pu16(&mem.dst(), 0) || // flags, not used by GnuPG + !pu16(&mem.dst(), 0)) { // RFU + return false; + } + + // same as above, for each subkey + std::vector<uint32_t> subkey_sig_expirations; + for (auto &sfp : key->subkey_fps()) { + pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp); + if (!subkey || !pbuf(&mem.dst(), subkey->fp().fingerprint, PGP_FINGERPRINT_SIZE) || + !pu32(&mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4) + !pu16(&mem.dst(), 0) || // flags, not used by GnuPG + !pu16(&mem.dst(), 0)) { // RFU + return false; + } + // load signature expirations while we're at it + for (size_t i = 0; i < subkey->sig_count(); i++) { + uint32_t expiration = subkey->get_sig(i).sig.key_expiration(); + subkey_sig_expirations.push_back(expiration); + } + } + + if (!pu16(&mem.dst(), 0)) { // Zero size of serial number + return false; + } + + // skip serial number + if (!pu16(&mem.dst(), key->uid_count()) || !pu16(&mem.dst(), 12)) { + return false; + } + + size_t uid_start = mem.writeb(); + for (size_t i = 0; i < key->uid_count(); i++) { + if (!pu32(&mem.dst(), 0) || + !pu32(&mem.dst(), 0)) { // UID offset and length, update when blob has done + return false; + } + + if (!pu16(&mem.dst(), 0)) { // flags, (not yet used) + return false; + } + + if (!pu8(&mem.dst(), 0) || !pu8(&mem.dst(), 0)) { // Validity & RFU + return false; + } + } + + if (!pu16(&mem.dst(), key->sig_count() + subkey_sig_expirations.size()) || + !pu16(&mem.dst(), 4)) { + return false; + } + + for (size_t i = 0; i < key->sig_count(); i++) { + if (!pu32(&mem.dst(), key->get_sig(i).sig.key_expiration())) { + return false; + } + } + for (auto &expiration : subkey_sig_expirations) { + if (!pu32(&mem.dst(), expiration)) { + return false; + } + } + + if (!pu8(&mem.dst(), 0) || + !pu8(&mem.dst(), 0)) { // Assigned ownertrust & All_Validity (not yet used) + return false; + } + + if (!pu16(&mem.dst(), 0) || !pu32(&mem.dst(), 0)) { // RFU & Recheck_after + return false; + } + + if (!pu32(&mem.dst(), key_store->secctx.time()) || + !pu32(&mem.dst(), key_store->secctx.time())) { // Latest timestamp && created + return false; + } + + if (!pu32(&mem.dst(), 0)) { // Size of reserved space + return false; + } + + // wrtite UID, we might redesign PGP write and use this information from keyblob + for (size_t i = 0; i < key->uid_count(); i++) { + const pgp_userid_t &uid = key->get_uid(i); + uint8_t * p = (uint8_t *) mem.memory() + uid_start + (12 * i); + /* store absolute uid offset in the output stream */ + uint32_t pt = mem.writeb() + dst->writeb; + STORE32BE(p, pt); + /* and uid length */ + pt = uid.str.size(); + STORE32BE(p + 4, pt); + /* uid data itself */ + if (!pbuf(&mem.dst(), uid.str.c_str(), pt)) { + return false; + } + } + + /* write keyblock and fix the offset/length */ + size_t key_start = mem.writeb(); + uint32_t pt = key_start; + uint8_t *p = (uint8_t *) mem.memory() + 8; + STORE32BE(p, pt); + + key->write(mem.dst()); + if (mem.werr()) { + return false; + } + + for (auto &sfp : key->subkey_fps()) { + const pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp); + if (!subkey) { + return false; + } + subkey->write(mem.dst()); + if (mem.werr()) { + return false; + } + } + + /* key blob length */ + pt = mem.writeb() - key_start; + p = (uint8_t *) mem.memory() + 12; + STORE32BE(p, pt); + + // fix the length of blob + pt = mem.writeb() + 20; + p = (uint8_t *) mem.memory(); + STORE32BE(p, pt); + + // checksum + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + hash->add(mem.memory(), mem.writeb()); + uint8_t checksum[PGP_SHA1_HASH_SIZE]; + assert(hash->size() == sizeof(checksum)); + hash->finish(checksum); + + if (!(pbuf(&mem.dst(), checksum, PGP_SHA1_HASH_SIZE))) { + return false; + } + + /* finally write to the output */ + dst_write(dst, mem.memory(), mem.writeb()); + return !dst->werr; +} + +static bool +rnp_key_store_kbx_write_x509(rnp_key_store_t *key_store, pgp_dest_t *dst) +{ + for (auto &blob : key_store->blobs) { + if (blob->type() != KBX_X509_BLOB) { + continue; + } + if (!pbuf(dst, blob->image().data(), blob->length())) { + return false; + } + } + return true; +} + +bool +rnp_key_store_kbx_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst) +{ + try { + if (!rnp_key_store_kbx_write_header(key_store, dst)) { + RNP_LOG("Can't write KBX header"); + return false; + } + + for (auto &key : key_store->keys) { + if (!key.is_primary()) { + continue; + } + if (!rnp_key_store_kbx_write_pgp(key_store, &key, dst)) { + RNP_LOG("Can't write PGP blobs for key %p", &key); + return false; + } + } + + if (!rnp_key_store_kbx_write_x509(key_store, dst)) { + RNP_LOG("Can't write X509 blobs"); + return false; + } + return true; + } catch (const std::exception &e) { + RNP_LOG("Failed to write KBX store: %s", e.what()); + return false; + } +} diff --git a/src/librekey/key_store_kbx.h b/src/librekey/key_store_kbx.h new file mode 100644 index 0000000..68d725d --- /dev/null +++ b/src/librekey/key_store_kbx.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_KEY_STORE_KBX_H +#define RNP_KEY_STORE_KBX_H + +#include <rekey/rnp_key_store.h> +#include "sec_profile.hpp" + +bool rnp_key_store_kbx_from_src(rnp_key_store_t *, pgp_source_t *, const pgp_key_provider_t *); +bool rnp_key_store_kbx_to_dst(rnp_key_store_t *, pgp_dest_t *); + +#endif // RNP_KEY_STORE_KBX_H diff --git a/src/librekey/key_store_pgp.cpp b/src/librekey/key_store_pgp.cpp new file mode 100644 index 0000000..6edc099 --- /dev/null +++ b/src/librekey/key_store_pgp.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(__NetBSD__) +__COPYRIGHT("@(#) Copyright (c) 2009 The NetBSD Foundation, Inc. All rights reserved."); +__RCSID("$NetBSD: keyring.c,v 1.50 2011/06/25 00:37:44 agc Exp $"); +#endif + +#include <stdlib.h> +#include <string.h> + +#include <librepgp/stream-common.h> +#include <librepgp/stream-sig.h> +#include <librepgp/stream-packet.h> +#include <librepgp/stream-key.h> +#include "crypto/mem.h" + +#include "types.h" +#include "key_store_pgp.h" +#include "pgp-key.h" + +bool +rnp_key_store_add_transferable_subkey(rnp_key_store_t * keyring, + pgp_transferable_subkey_t *tskey, + pgp_key_t * pkey) +{ + try { + /* create subkey */ + pgp_key_t skey(*tskey, pkey); + /* add it to the storage */ + return rnp_key_store_add_key(keyring, &skey); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + RNP_LOG_KEY_PKT("failed to create subkey %s", tskey->subkey); + RNP_LOG_KEY("primary key is %s", pkey); + return false; + } +} + +bool +rnp_key_store_add_transferable_key(rnp_key_store_t *keyring, pgp_transferable_key_t *tkey) +{ + pgp_key_t *addkey = NULL; + + /* create key from transferable key */ + try { + pgp_key_t key(*tkey); + /* temporary disable key validation */ + keyring->disable_validation = true; + /* add key to the storage before subkeys */ + addkey = rnp_key_store_add_key(keyring, &key); + } catch (const std::exception &e) { + keyring->disable_validation = false; + RNP_LOG_KEY_PKT("failed to add key %s", tkey->key); + return false; + } + + if (!addkey) { + keyring->disable_validation = false; + RNP_LOG("Failed to add key to key store."); + return false; + } + + /* add subkeys */ + for (auto &subkey : tkey->subkeys) { + if (!rnp_key_store_add_transferable_subkey(keyring, &subkey, addkey)) { + RNP_LOG("Failed to add subkey to key store."); + keyring->disable_validation = false; + goto error; + } + } + + /* now validate/refresh the whole key with subkeys */ + keyring->disable_validation = false; + addkey->revalidate(*keyring); + return true; +error: + /* during key addition all fields are copied so will be cleaned below */ + rnp_key_store_remove_key(keyring, addkey, false); + return false; +} + +rnp_result_t +rnp_key_store_pgp_read_key_from_src(rnp_key_store_t &keyring, + pgp_source_t & src, + bool skiperrors) +{ + pgp_transferable_key_t key; + rnp_result_t ret = process_pgp_key_auto(src, key, true, skiperrors); + + if (ret && (!skiperrors || (ret != RNP_ERROR_BAD_FORMAT))) { + return ret; + } + + /* check whether we have primary key */ + if (key.key.tag != PGP_PKT_RESERVED) { + return rnp_key_store_add_transferable_key(&keyring, &key) ? RNP_SUCCESS : + RNP_ERROR_BAD_STATE; + } + + /* we just skipped some unexpected packets and read nothing */ + if (key.subkeys.empty()) { + return RNP_SUCCESS; + } + + return rnp_key_store_add_transferable_subkey(&keyring, &key.subkeys.front(), NULL) ? + RNP_SUCCESS : + RNP_ERROR_BAD_STATE; +} + +rnp_result_t +rnp_key_store_pgp_read_from_src(rnp_key_store_t *keyring, pgp_source_t *src, bool skiperrors) +{ + /* check whether we have transferable subkey in source */ + if (is_subkey_pkt(stream_pkt_type(*src))) { + pgp_transferable_subkey_t tskey; + rnp_result_t ret = process_pgp_subkey(*src, tskey, skiperrors); + if (ret) { + return ret; + } + return rnp_key_store_add_transferable_subkey(keyring, &tskey, NULL) ? + RNP_SUCCESS : + RNP_ERROR_BAD_STATE; + } + + /* process armored or raw transferable key packets sequence(s) */ + try { + pgp_key_sequence_t keys; + rnp_result_t ret = process_pgp_keys(*src, keys, skiperrors); + if (ret) { + return ret; + } + for (auto &key : keys.keys) { + if (!rnp_key_store_add_transferable_key(keyring, &key)) { + return RNP_ERROR_BAD_STATE; + } + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_PARAMETERS; + } +} + +std::vector<uint8_t> +rnp_key_to_vec(const pgp_key_t &key) +{ + rnp::MemoryDest dst; + key.write(dst.dst()); + return dst.to_vector(); +} + +static bool +do_write(rnp_key_store_t *key_store, pgp_dest_t *dst, bool secret) +{ + for (auto &key : key_store->keys) { + if (key.is_secret() != secret) { + continue; + } + // skip subkeys, they are written below (orphans are ignored) + if (!key.is_primary()) { + continue; + } + + if (key.format != PGP_KEY_STORE_GPG) { + RNP_LOG("incorrect format (conversions not supported): %d", key.format); + return false; + } + key.write(*dst); + if (dst->werr) { + return false; + } + for (auto &sfp : key.subkey_fps()) { + pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp); + if (!subkey) { + RNP_LOG("Missing subkey"); + continue; + } + subkey->write(*dst); + if (dst->werr) { + return false; + } + } + } + return true; +} + +bool +rnp_key_store_pgp_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst) +{ + // two separate passes (public keys, then secret keys) + return do_write(key_store, dst, false) && do_write(key_store, dst, true); +} diff --git a/src/librekey/key_store_pgp.h b/src/librekey/key_store_pgp.h new file mode 100644 index 0000000..d3dcd06 --- /dev/null +++ b/src/librekey/key_store_pgp.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** \file + */ + +#ifndef KEY_STORE_PGP_H_ +#define KEY_STORE_PGP_H_ + +#include <rekey/rnp_key_store.h> +#include <librepgp/stream-common.h> +#include <librepgp/stream-key.h> + +/* Read the whole keyring from the src, processing all available keys or subkeys */ +rnp_result_t rnp_key_store_pgp_read_from_src(rnp_key_store_t *keyring, + pgp_source_t * src, + bool skiperrors = false); + +/* Read the first key or subkey from the src */ +rnp_result_t rnp_key_store_pgp_read_key_from_src(rnp_key_store_t &keyring, + pgp_source_t & src, + bool skiperrors = false); + +bool rnp_key_store_pgp_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst); + +bool rnp_key_store_add_transferable_subkey(rnp_key_store_t * keyring, + pgp_transferable_subkey_t *tskey, + pgp_key_t * pkey); + +bool rnp_key_store_add_transferable_key(rnp_key_store_t * keyring, + pgp_transferable_key_t *tkey); + +std::vector<uint8_t> rnp_key_to_vec(const pgp_key_t &key); + +#endif /* KEY_STORE_PGP_H_ */ diff --git a/src/librekey/rnp_key_store.cpp b/src/librekey/rnp_key_store.cpp new file mode 100644 index 0000000..002a51e --- /dev/null +++ b/src/librekey/rnp_key_store.cpp @@ -0,0 +1,803 @@ +/* + * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <sys/types.h> +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#else +#include "uniwin.h" +#endif + +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <stdlib.h> +#include <dirent.h> +#include <errno.h> +#include <algorithm> +#include <stdexcept> + +#include <rekey/rnp_key_store.h> +#include <librepgp/stream-packet.h> + +#include "key_store_pgp.h" +#include "key_store_kbx.h" +#include "key_store_g10.h" +#include "kbx_blob.hpp" + +#include "pgp-key.h" +#include "fingerprint.h" +#include "crypto/hash.hpp" +#include "crypto/mem.h" +#include "file-utils.h" +#ifdef _WIN32 +#include "str-utils.h" +#endif + +bool +rnp_key_store_load_from_path(rnp_key_store_t * key_store, + const pgp_key_provider_t *key_provider) +{ + pgp_source_t src = {}; + + if (key_store->format == PGP_KEY_STORE_G10) { + auto dir = rnp_opendir(key_store->path.c_str()); + if (!dir) { + RNP_LOG( + "Can't open G10 directory %s: %s", key_store->path.c_str(), strerror(errno)); + return false; + } + + std::string dirname; + while (!((dirname = rnp_readdir_name(dir)).empty())) { + std::string path = rnp::path::append(key_store->path, dirname); + + if (init_file_src(&src, path.c_str())) { + RNP_LOG("failed to read file %s", path.c_str()); + continue; + } + // G10 may fail to read one file, so ignore it! + if (!rnp_key_store_g10_from_src(key_store, &src, key_provider)) { + RNP_LOG("Can't parse file: %s", path.c_str()); // TODO: %S ? + } + src_close(&src); + } + rnp_closedir(dir); + return true; + } + + /* init file source and load from it */ + if (init_file_src(&src, key_store->path.c_str())) { + RNP_LOG("failed to read file %s", key_store->path.c_str()); + return false; + } + + bool rc = rnp_key_store_load_from_src(key_store, &src, key_provider); + src_close(&src); + return rc; +} + +bool +rnp_key_store_load_from_src(rnp_key_store_t * key_store, + pgp_source_t * src, + const pgp_key_provider_t *key_provider) +{ + switch (key_store->format) { + case PGP_KEY_STORE_GPG: + return rnp_key_store_pgp_read_from_src(key_store, src) == RNP_SUCCESS; + case PGP_KEY_STORE_KBX: + return rnp_key_store_kbx_from_src(key_store, src, key_provider); + case PGP_KEY_STORE_G10: + return rnp_key_store_g10_from_src(key_store, src, key_provider); + default: + RNP_LOG("Unsupported load from memory for key-store format: %d", key_store->format); + } + + return false; +} + +bool +rnp_key_store_write_to_path(rnp_key_store_t *key_store) +{ + bool rc; + pgp_dest_t keydst = {}; + + /* write g10 key store to the directory */ + if (key_store->format == PGP_KEY_STORE_G10) { + char path[MAXPATHLEN]; + + struct stat path_stat; + if (rnp_stat(key_store->path.c_str(), &path_stat) != -1) { + if (!S_ISDIR(path_stat.st_mode)) { + RNP_LOG("G10 keystore should be a directory: %s", key_store->path.c_str()); + return false; + } + } else { + if (errno != ENOENT) { + RNP_LOG("stat(%s): %s", key_store->path.c_str(), strerror(errno)); + return false; + } + if (RNP_MKDIR(key_store->path.c_str(), S_IRWXU) != 0) { + RNP_LOG("mkdir(%s, S_IRWXU): %s", key_store->path.c_str(), strerror(errno)); + return false; + } + } + + for (auto &key : key_store->keys) { + char grip[PGP_FINGERPRINT_HEX_SIZE] = {0}; + rnp::hex_encode(key.grip().data(), key.grip().size(), grip, sizeof(grip)); + snprintf(path, sizeof(path), "%s/%s.key", key_store->path.c_str(), grip); + + if (init_tmpfile_dest(&keydst, path, true)) { + RNP_LOG("failed to create file"); + return false; + } + + if (!rnp_key_store_gnupg_sexp_to_dst(&key, &keydst)) { + RNP_LOG("failed to write key to file"); + dst_close(&keydst, true); + return false; + } + + rc = dst_finish(&keydst) == RNP_SUCCESS; + dst_close(&keydst, !rc); + + if (!rc) { + return false; + } + } + + return true; + } + + /* write kbx/gpg store to the single file */ + if (init_tmpfile_dest(&keydst, key_store->path.c_str(), true)) { + RNP_LOG("failed to create keystore file"); + return false; + } + + if (!rnp_key_store_write_to_dst(key_store, &keydst)) { + RNP_LOG("failed to write keys to file"); + dst_close(&keydst, true); + return false; + } + + rc = dst_finish(&keydst) == RNP_SUCCESS; + dst_close(&keydst, !rc); + return rc; +} + +bool +rnp_key_store_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst) +{ + switch (key_store->format) { + case PGP_KEY_STORE_GPG: + return rnp_key_store_pgp_write_to_dst(key_store, dst); + case PGP_KEY_STORE_KBX: + return rnp_key_store_kbx_to_dst(key_store, dst); + default: + RNP_LOG("Unsupported write to memory for key-store format: %d", key_store->format); + } + + return false; +} + +void +rnp_key_store_clear(rnp_key_store_t *keyring) +{ + keyring->keybyfp.clear(); + keyring->keys.clear(); + keyring->blobs.clear(); +} + +size_t +rnp_key_store_get_key_count(const rnp_key_store_t *keyring) +{ + return keyring->keys.size(); +} + +static bool +rnp_key_store_refresh_subkey_grips(rnp_key_store_t *keyring, pgp_key_t *key) +{ + if (key->is_subkey()) { + RNP_LOG("wrong argument"); + return false; + } + + for (auto &skey : keyring->keys) { + bool found = false; + + /* if we have primary_grip then we also added to subkey_grips */ + if (!skey.is_subkey() || skey.has_primary_fp()) { + continue; + } + + for (size_t i = 0; i < skey.sig_count(); i++) { + const pgp_subsig_t &subsig = skey.get_sig(i); + + if (subsig.sig.type() != PGP_SIG_SUBKEY) { + continue; + } + if (subsig.sig.has_keyfp() && (key->fp() == subsig.sig.keyfp())) { + found = true; + break; + } + if (subsig.sig.has_keyid() && (key->keyid() == subsig.sig.keyid())) { + found = true; + break; + } + } + + if (found) { + try { + key->link_subkey_fp(skey); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + } + } + + return true; +} + +static pgp_key_t * +rnp_key_store_add_subkey(rnp_key_store_t *keyring, pgp_key_t *srckey, pgp_key_t *oldkey) +{ + pgp_key_t *primary = NULL; + if (oldkey) { + primary = rnp_key_store_get_primary_key(keyring, oldkey); + } + if (!primary) { + primary = rnp_key_store_get_primary_key(keyring, srckey); + } + + if (oldkey) { + /* check for the weird case when same subkey has different primary keys */ + if (srckey->has_primary_fp() && oldkey->has_primary_fp() && + (srckey->primary_fp() != oldkey->primary_fp())) { + RNP_LOG_KEY("Warning: different primary keys for subkey %s", srckey); + pgp_key_t *srcprim = rnp_key_store_get_key_by_fpr(keyring, srckey->primary_fp()); + if (srcprim && (srcprim != primary)) { + srcprim->remove_subkey_fp(srckey->fp()); + } + } + /* in case we already have key let's merge it in */ + if (!oldkey->merge(*srckey, primary)) { + RNP_LOG_KEY("failed to merge subkey %s", srckey); + RNP_LOG_KEY("primary key is %s", primary); + return NULL; + } + } else { + try { + keyring->keys.emplace_back(); + oldkey = &keyring->keys.back(); + keyring->keybyfp[srckey->fp()] = std::prev(keyring->keys.end()); + *oldkey = pgp_key_t(*srckey); + if (primary) { + primary->link_subkey_fp(*oldkey); + } + } catch (const std::exception &e) { + RNP_LOG_KEY("key %s copying failed", srckey); + RNP_LOG_KEY("primary key is %s", primary); + RNP_LOG("%s", e.what()); + if (oldkey) { + keyring->keys.pop_back(); + keyring->keybyfp.erase(srckey->fp()); + } + return NULL; + } + } + + /* validate all added keys if not disabled */ + if (!keyring->disable_validation && !oldkey->validated()) { + oldkey->validate_subkey(primary, keyring->secctx); + } + if (!oldkey->refresh_data(primary, keyring->secctx)) { + RNP_LOG_KEY("Failed to refresh subkey %s data", srckey); + RNP_LOG_KEY("primary key is %s", primary); + } + return oldkey; +} + +/* add a key to keyring */ +pgp_key_t * +rnp_key_store_add_key(rnp_key_store_t *keyring, pgp_key_t *srckey) +{ + assert(srckey->type() && srckey->version()); + pgp_key_t *added_key = rnp_key_store_get_key_by_fpr(keyring, srckey->fp()); + /* we cannot merge G10 keys - so just return it */ + if (added_key && (srckey->format == PGP_KEY_STORE_G10)) { + return added_key; + } + /* different processing for subkeys */ + if (srckey->is_subkey()) { + return rnp_key_store_add_subkey(keyring, srckey, added_key); + } + + if (added_key) { + if (!added_key->merge(*srckey)) { + RNP_LOG_KEY("failed to merge key %s", srckey); + return NULL; + } + } else { + try { + keyring->keys.emplace_back(); + added_key = &keyring->keys.back(); + keyring->keybyfp[srckey->fp()] = std::prev(keyring->keys.end()); + *added_key = pgp_key_t(*srckey); + /* primary key may be added after subkeys, so let's handle this case correctly */ + if (!rnp_key_store_refresh_subkey_grips(keyring, added_key)) { + RNP_LOG_KEY("failed to refresh subkey grips for %s", added_key); + } + } catch (const std::exception &e) { + RNP_LOG_KEY("key %s copying failed", srckey); + RNP_LOG("%s", e.what()); + if (added_key) { + keyring->keys.pop_back(); + keyring->keybyfp.erase(srckey->fp()); + } + return NULL; + } + } + + /* validate all added keys if not disabled or already validated */ + if (!keyring->disable_validation && !added_key->validated()) { + added_key->revalidate(*keyring); + } else if (!added_key->refresh_data(keyring->secctx)) { + RNP_LOG_KEY("Failed to refresh key %s data", srckey); + } + return added_key; +} + +pgp_key_t * +rnp_key_store_import_key(rnp_key_store_t * keyring, + pgp_key_t * srckey, + bool pubkey, + pgp_key_import_status_t *status) +{ + /* add public key */ + pgp_key_t *exkey = rnp_key_store_get_key_by_fpr(keyring, srckey->fp()); + size_t expackets = exkey ? exkey->rawpkt_count() : 0; + try { + pgp_key_t keycp(*srckey, pubkey); + keyring->disable_validation = true; + exkey = rnp_key_store_add_key(keyring, &keycp); + keyring->disable_validation = false; + if (!exkey) { + RNP_LOG("failed to add key to the keyring"); + return NULL; + } + bool changed = exkey->rawpkt_count() > expackets; + if (changed || !exkey->validated()) { + /* this will revalidated primary key with all subkeys */ + exkey->revalidate(*keyring); + } + if (status) { + *status = changed ? (expackets ? PGP_KEY_IMPORT_STATUS_UPDATED : + PGP_KEY_IMPORT_STATUS_NEW) : + PGP_KEY_IMPORT_STATUS_UNCHANGED; + } + return exkey; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + keyring->disable_validation = false; + return NULL; + } +} + +pgp_key_t * +rnp_key_store_get_signer_key(rnp_key_store_t *store, const pgp_signature_t *sig) +{ + pgp_key_search_t search; + // prefer using the issuer fingerprint when available + if (sig->has_keyfp()) { + search.by.fingerprint = sig->keyfp(); + search.type = PGP_KEY_SEARCH_FINGERPRINT; + return rnp_key_store_search(store, &search, NULL); + } + // fall back to key id search + if (sig->has_keyid()) { + search.by.keyid = sig->keyid(); + search.type = PGP_KEY_SEARCH_KEYID; + return rnp_key_store_search(store, &search, NULL); + } + return NULL; +} + +static pgp_sig_import_status_t +rnp_key_store_import_subkey_signature(rnp_key_store_t * keyring, + pgp_key_t * key, + const pgp_signature_t *sig) +{ + if ((sig->type() != PGP_SIG_SUBKEY) && (sig->type() != PGP_SIG_REV_SUBKEY)) { + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + pgp_key_t *primary = rnp_key_store_get_signer_key(keyring, sig); + if (!primary || !key->has_primary_fp()) { + RNP_LOG("No primary grip or primary key"); + return PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY; + } + if (primary->fp() != key->primary_fp()) { + RNP_LOG("Wrong subkey signature's signer."); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + + try { + pgp_key_t tmpkey(key->pkt()); + tmpkey.add_sig(*sig); + if (!tmpkey.refresh_data(primary, keyring->secctx)) { + RNP_LOG("Failed to add signature to the key."); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + + size_t expackets = key->rawpkt_count(); + key = rnp_key_store_add_key(keyring, &tmpkey); + if (!key) { + RNP_LOG("Failed to add key with imported sig to the keyring"); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + return (key->rawpkt_count() > expackets) ? PGP_SIG_IMPORT_STATUS_NEW : + PGP_SIG_IMPORT_STATUS_UNCHANGED; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } +} + +pgp_sig_import_status_t +rnp_key_store_import_key_signature(rnp_key_store_t * keyring, + pgp_key_t * key, + const pgp_signature_t *sig) +{ + if (key->is_subkey()) { + return rnp_key_store_import_subkey_signature(keyring, key, sig); + } + if ((sig->type() != PGP_SIG_DIRECT) && (sig->type() != PGP_SIG_REV_KEY)) { + RNP_LOG("Wrong signature type: %d", (int) sig->type()); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + + try { + pgp_key_t tmpkey(key->pkt()); + tmpkey.add_sig(*sig); + if (!tmpkey.refresh_data(keyring->secctx)) { + RNP_LOG("Failed to add signature to the key."); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + + size_t expackets = key->rawpkt_count(); + key = rnp_key_store_add_key(keyring, &tmpkey); + if (!key) { + RNP_LOG("Failed to add key with imported sig to the keyring"); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + return (key->rawpkt_count() > expackets) ? PGP_SIG_IMPORT_STATUS_NEW : + PGP_SIG_IMPORT_STATUS_UNCHANGED; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } +} + +pgp_key_t * +rnp_key_store_import_signature(rnp_key_store_t * keyring, + const pgp_signature_t * sig, + pgp_sig_import_status_t *status) +{ + pgp_sig_import_status_t tmp_status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + if (!status) { + status = &tmp_status; + } + *status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + + /* we support only direct-key and key revocation signatures here */ + if ((sig->type() != PGP_SIG_DIRECT) && (sig->type() != PGP_SIG_REV_KEY)) { + return NULL; + } + + pgp_key_t *res_key = rnp_key_store_get_signer_key(keyring, sig); + if (!res_key || !res_key->is_primary()) { + *status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY; + return NULL; + } + *status = rnp_key_store_import_key_signature(keyring, res_key, sig); + return res_key; +} + +bool +rnp_key_store_remove_key(rnp_key_store_t *keyring, const pgp_key_t *key, bool subkeys) +{ + auto it = keyring->keybyfp.find(key->fp()); + if (it == keyring->keybyfp.end()) { + return false; + } + + /* cleanup primary_grip (or subkey)/subkey_grips */ + if (key->is_primary() && key->subkey_count()) { + for (size_t i = 0; i < key->subkey_count(); i++) { + auto it = keyring->keybyfp.find(key->get_subkey_fp(i)); + if (it == keyring->keybyfp.end()) { + continue; + } + /* if subkeys are deleted then no need to update grips */ + if (subkeys) { + keyring->keys.erase(it->second); + keyring->keybyfp.erase(it); + continue; + } + it->second->unset_primary_fp(); + } + } + if (key->is_subkey() && key->has_primary_fp()) { + pgp_key_t *primary = rnp_key_store_get_primary_key(keyring, key); + if (primary) { + primary->remove_subkey_fp(key->fp()); + } + } + + keyring->keys.erase(it->second); + keyring->keybyfp.erase(it); + return true; +} + +const pgp_key_t * +rnp_key_store_get_key_by_fpr(const rnp_key_store_t *keyring, const pgp_fingerprint_t &fpr) +{ + auto it = keyring->keybyfp.find(fpr); + if (it == keyring->keybyfp.end()) { + return NULL; + } + return &*it->second; +} + +pgp_key_t * +rnp_key_store_get_key_by_fpr(rnp_key_store_t *keyring, const pgp_fingerprint_t &fpr) +{ + auto it = keyring->keybyfp.find(fpr); + if (it == keyring->keybyfp.end()) { + return NULL; + } + return &*it->second; +} + +pgp_key_t * +rnp_key_store_get_primary_key(rnp_key_store_t *keyring, const pgp_key_t *subkey) +{ + if (!subkey->is_subkey()) { + return NULL; + } + + if (subkey->has_primary_fp()) { + pgp_key_t *primary = rnp_key_store_get_key_by_fpr(keyring, subkey->primary_fp()); + return primary && primary->is_primary() ? primary : NULL; + } + + for (size_t i = 0; i < subkey->sig_count(); i++) { + const pgp_subsig_t &subsig = subkey->get_sig(i); + if (subsig.sig.type() != PGP_SIG_SUBKEY) { + continue; + } + + pgp_key_t *primary = rnp_key_store_get_signer_key(keyring, &subsig.sig); + if (primary && primary->is_primary()) { + return primary; + } + } + return NULL; +} + +static void +grip_hash_mpi(rnp::Hash &hash, const pgp_mpi_t &val, const char name, bool lzero = true) +{ + size_t len = mpi_bytes(&val); + size_t idx = 0; + for (idx = 0; (idx < len) && !val.mpi[idx]; idx++) + ; + + if (name) { + size_t hlen = idx >= len ? 0 : len - idx; + if ((len > idx) && lzero && (val.mpi[idx] & 0x80)) { + hlen++; + } + + char buf[20] = {0}; + snprintf(buf, sizeof(buf), "(1:%c%zu:", name, hlen); + hash.add(buf, strlen(buf)); + } + + if (idx < len) { + /* gcrypt prepends mpis with zero if higher bit is set */ + if (lzero && (val.mpi[idx] & 0x80)) { + uint8_t zero = 0; + hash.add(&zero, 1); + } + hash.add(val.mpi + idx, len - idx); + } + if (name) { + hash.add(")", 1); + } +} + +static void +grip_hash_ecc_hex(rnp::Hash &hash, const char *hex, char name) +{ + pgp_mpi_t mpi = {}; + mpi.len = rnp::hex_decode(hex, mpi.mpi, sizeof(mpi.mpi)); + if (!mpi.len) { + RNP_LOG("wrong hex mpi"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + /* libgcrypt doesn't add leading zero when hashes ecc mpis */ + return grip_hash_mpi(hash, mpi, name, false); +} + +static void +grip_hash_ec(rnp::Hash &hash, const pgp_ec_key_t &key) +{ + const ec_curve_desc_t *desc = get_curve_desc(key.curve); + if (!desc) { + RNP_LOG("unknown curve %d", (int) key.curve); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + /* build uncompressed point from gx and gy */ + pgp_mpi_t g = {}; + g.mpi[0] = 0x04; + g.len = 1; + size_t len = rnp::hex_decode(desc->gx, g.mpi + g.len, sizeof(g.mpi) - g.len); + if (!len) { + RNP_LOG("wrong x mpi"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + g.len += len; + len = rnp::hex_decode(desc->gy, g.mpi + g.len, sizeof(g.mpi) - g.len); + if (!len) { + RNP_LOG("wrong y mpi"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + g.len += len; + + /* p, a, b, g, n, q */ + grip_hash_ecc_hex(hash, desc->p, 'p'); + grip_hash_ecc_hex(hash, desc->a, 'a'); + grip_hash_ecc_hex(hash, desc->b, 'b'); + grip_hash_mpi(hash, g, 'g', false); + grip_hash_ecc_hex(hash, desc->n, 'n'); + + if ((key.curve == PGP_CURVE_ED25519) || (key.curve == PGP_CURVE_25519)) { + if (g.len < 1) { + RNP_LOG("wrong 25519 p"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + g.len = key.p.len - 1; + memcpy(g.mpi, key.p.mpi + 1, g.len); + grip_hash_mpi(hash, g, 'q', false); + } else { + grip_hash_mpi(hash, key.p, 'q', false); + } +} + +/* keygrip is subjectKeyHash from pkcs#15 for RSA. */ +bool +rnp_key_store_get_key_grip(const pgp_key_material_t *key, pgp_key_grip_t &grip) +{ + try { + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + switch (key->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_SIGN_ONLY: + case PGP_PKA_RSA_ENCRYPT_ONLY: + grip_hash_mpi(*hash, key->rsa.n, '\0'); + break; + case PGP_PKA_DSA: + grip_hash_mpi(*hash, key->dsa.p, 'p'); + grip_hash_mpi(*hash, key->dsa.q, 'q'); + grip_hash_mpi(*hash, key->dsa.g, 'g'); + grip_hash_mpi(*hash, key->dsa.y, 'y'); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + grip_hash_mpi(*hash, key->eg.p, 'p'); + grip_hash_mpi(*hash, key->eg.g, 'g'); + grip_hash_mpi(*hash, key->eg.y, 'y'); + break; + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + grip_hash_ec(*hash, key->ec); + break; + default: + RNP_LOG("unsupported public-key algorithm %d", (int) key->alg); + return false; + } + return hash->finish(grip.data()) == grip.size(); + } catch (const std::exception &e) { + RNP_LOG("Grip calculation failed: %s", e.what()); + return false; + } +} + +pgp_key_t * +rnp_key_store_search(rnp_key_store_t * keyring, + const pgp_key_search_t *search, + pgp_key_t * after) +{ + // since keys are distinguished by fingerprint then just do map lookup + if (search->type == PGP_KEY_SEARCH_FINGERPRINT) { + pgp_key_t *key = rnp_key_store_get_key_by_fpr(keyring, search->by.fingerprint); + if (after && (after != key)) { + RNP_LOG("searching with invalid after param"); + return NULL; + } + // return NULL if after is specified + return after ? NULL : key; + } + + // if after is provided, make sure it is a member of the appropriate list + auto it = + std::find_if(keyring->keys.begin(), keyring->keys.end(), [after](const pgp_key_t &key) { + return !after || (after == &key); + }); + if (after && (it == keyring->keys.end())) { + RNP_LOG("searching with non-keyrings after param"); + return NULL; + } + if (after) { + it = std::next(it); + } + it = std::find_if(it, keyring->keys.end(), [search](const pgp_key_t &key) { + return rnp_key_matches_search(&key, search); + }); + return (it == keyring->keys.end()) ? NULL : &(*it); +} + +rnp_key_store_t::rnp_key_store_t(pgp_key_store_format_t _format, + const std::string & _path, + rnp::SecurityContext & ctx) + : secctx(ctx) +{ + if (_format == PGP_KEY_STORE_UNKNOWN) { + RNP_LOG("Invalid key store format"); + throw std::invalid_argument("format"); + } + format = _format; + path = _path; +} + +rnp_key_store_t::~rnp_key_store_t() +{ + rnp_key_store_clear(this); +} diff --git a/src/librepgp/stream-armor.cpp b/src/librepgp/stream-armor.cpp new file mode 100644 index 0000000..669c305 --- /dev/null +++ b/src/librepgp/stream-armor.cpp @@ -0,0 +1,1287 @@ +/* + * Copyright (c) 2017-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include <algorithm> +#include "stream-def.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "str-utils.h" +#include "crypto/hash.hpp" +#include "utils.h" + +#define ARMORED_BLOCK_SIZE (4096) +#define ARMORED_PEEK_BUF_SIZE 1024 +#define ARMORED_MIN_LINE_LENGTH (16) +#define ARMORED_MAX_LINE_LENGTH (76) + +typedef struct pgp_source_armored_param_t { + pgp_source_t * readsrc; /* source to read from */ + pgp_armored_msg_t type; /* type of the message */ + char * armorhdr; /* armor header */ + char * version; /* Version: header if any */ + char * comment; /* Comment: header if any */ + char * hash; /* Hash: header if any */ + char * charset; /* Charset: header if any */ + uint8_t rest[ARMORED_BLOCK_SIZE]; /* unread decoded bytes, makes implementation easier */ + unsigned restlen; /* number of bytes in rest */ + unsigned restpos; /* index of first unread byte in rest, restpos <= restlen */ + uint8_t brest[3]; /* decoded 6-bit tail bytes */ + unsigned brestlen; /* number of bytes in brest */ + bool eofb64; /* end of base64 stream reached */ + uint8_t readcrc[3]; /* crc-24 from the armored data */ + bool has_crc; /* message contains CRC line */ + std::unique_ptr<rnp::CRC24> crc_ctx; /* CTX used to calculate CRC */ + bool noheaders; /* only base64 data, no headers */ +} pgp_source_armored_param_t; + +typedef struct pgp_dest_armored_param_t { + pgp_dest_t * writedst; + pgp_armored_msg_t type; /* type of the message */ + char eol[2]; /* end of line, all non-zeroes are written */ + unsigned lout; /* chars written in current line */ + unsigned llen; /* length of the base64 line, defaults to 76 as per RFC */ + uint8_t tail[2]; /* bytes which didn't fit into 3-byte boundary */ + unsigned tailc; /* number of bytes in tail */ + std::unique_ptr<rnp::CRC24> crc_ctx; /* CTX used to calculate CRC */ +} pgp_dest_armored_param_t; + +/* + Table for base64 lookups: + 0xff - wrong character, + 0xfe - '=' + 0xfd - eol/whitespace, + 0..0x3f - represented 6-bit number +*/ +static const uint8_t B64DEC[256] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xfd, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, + 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, + 0xff, 0xfe, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff}; + +static bool +armor_read_padding(pgp_source_armored_param_t *param, size_t *read) +{ + char st[64]; + size_t stlen = 0; + + if (!src_peek_line(param->readsrc, st, 64, &stlen)) { + return false; + } + + if ((stlen == 1) || (stlen == 2)) { + if ((st[0] != CH_EQ) || ((stlen == 2) && (st[1] != CH_EQ))) { + return false; + } + + *read = stlen; + src_skip(param->readsrc, stlen); + return src_skip_eol(param->readsrc); + } else if (stlen == 5) { + *read = 0; + return true; + } else if ((stlen > 5) && !memcmp(st, ST_DASHES, 5)) { + /* case with absent crc and 3-byte last chunk */ + *read = 0; + return true; + } + return false; +} + +static bool +base64_read_padding(pgp_source_armored_param_t *param, size_t *read) +{ + char pad[16]; + size_t padlen = sizeof(pad); + + /* we would allow arbitrary number of whitespaces/eols after the padding */ + if (!src_read(param->readsrc, pad, padlen, &padlen)) { + return false; + } + /* strip trailing whitespaces */ + while (padlen && (B64DEC[(int) pad[padlen - 1]] == 0xfd)) { + padlen--; + } + /* check for '=' */ + for (size_t i = 0; i < padlen; i++) { + if (pad[i] != CH_EQ) { + RNP_LOG("wrong base64 padding: %.*s", (int) padlen, pad); + return false; + } + } + if (padlen > 2) { + RNP_LOG("wrong base64 padding length %zu.", padlen); + return false; + } + if (!src_eof(param->readsrc)) { + RNP_LOG("warning: extra data after the base64 stream."); + } + *read = padlen; + return true; +} + +static bool +armor_read_crc(pgp_source_t *src) +{ + uint8_t dec[4] = {0}; + char crc[8] = {0}; + size_t clen = 0; + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (!src_peek_line(param->readsrc, crc, sizeof(crc), &clen)) { + return false; + } + + if ((clen != 5) || (crc[0] != CH_EQ)) { + return false; + } + + for (int i = 0; i < 4; i++) { + if ((dec[i] = B64DEC[(uint8_t) crc[i + 1]]) >= 64) { + return false; + } + } + + param->readcrc[0] = (dec[0] << 2) | ((dec[1] >> 4) & 0x0F); + param->readcrc[1] = (dec[1] << 4) | ((dec[2] >> 2) & 0x0F); + param->readcrc[2] = (dec[2] << 6) | dec[3]; + + param->has_crc = true; + + src_skip(param->readsrc, 5); + return src_skip_eol(param->readsrc); +} + +static bool +armor_skip_chars(pgp_source_t *src, const char *chars) +{ + uint8_t ch; + size_t read; + + do { + bool found = false; + if (!src_peek(src, &ch, 1, &read)) { + return false; + } + if (!read) { + /* return true only if there is no underlying read error */ + return true; + } + for (const char *chptr = chars; *chptr; chptr++) { + if (ch == *chptr) { + src_skip(src, 1); + found = true; + break; + } + } + if (!found) { + break; + } + } while (1); + + return true; +} + +static bool +armor_read_trailer(pgp_source_t *src) +{ + char st[64]; + char str[64]; + size_t stlen; + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (!armor_skip_chars(param->readsrc, "\r\n")) { + return false; + } + + stlen = strlen(param->armorhdr); + if ((stlen > 5) && (stlen + 8 + 1 <= sizeof(st))) { + memcpy(st, ST_ARMOR_END, 8); /* 8 here is mandatory */ + memcpy(st + 8, param->armorhdr + 5, stlen - 5); + memcpy(st + stlen + 3, ST_DASHES, 5); + stlen += 8; + } else { + RNP_LOG("Internal error"); + return false; + } + if (!src_peek_eq(param->readsrc, str, stlen) || strncmp(str, st, stlen)) { + return false; + } + src_skip(param->readsrc, stlen); + (void) armor_skip_chars(param->readsrc, "\t "); + (void) src_skip_eol(param->readsrc); + return true; +} + +static bool +armored_update_crc(pgp_source_armored_param_t *param, + const void * buf, + size_t len, + bool finish = false) +{ + if (param->noheaders) { + return true; + } + try { + param->crc_ctx->add(buf, len); + if (!finish) { + return true; + } + auto crc = param->crc_ctx->finish(); + if (param->has_crc && memcmp(param->readcrc, crc.data(), 3)) { + RNP_LOG("Warning: CRC mismatch"); + } + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +static bool +armored_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + uint8_t b64buf[ARMORED_BLOCK_SIZE]; /* input base64 data with spaces and so on */ + uint8_t decbuf[ARMORED_BLOCK_SIZE + 4]; /* decoded 6-bit values */ + uint8_t *bufptr = (uint8_t *) buf; /* for better readability below */ + uint8_t *bptr, *bend; /* pointer to input data in b64buf */ + uint8_t *dptr, *dend, *pend; /* pointers to decoded data in decbuf: working pointer, last + available byte, last byte to process */ + uint8_t bval; + uint32_t b24; + size_t read = 0; + size_t left = len; + size_t eqcount = 0; /* number of '=' at the end of base64 stream */ + + if (!param) { + return false; + } + + /* checking whether there are some decoded bytes */ + if (param->restpos < param->restlen) { + if (param->restlen - param->restpos >= len) { + memcpy(bufptr, ¶m->rest[param->restpos], len); + param->restpos += len; + try { + param->crc_ctx->add(bufptr, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + *readres = len; + return true; + } else { + left = len - (param->restlen - param->restpos); + memcpy(bufptr, ¶m->rest[param->restpos], len - left); + param->restpos = param->restlen = 0; + bufptr += len - left; + } + } + + if (param->eofb64) { + *readres = len - left; + return true; + } + + memcpy(decbuf, param->brest, param->brestlen); + dend = decbuf + param->brestlen; + + do { + if (!src_peek(param->readsrc, b64buf, sizeof(b64buf), &read)) { + return false; + } + if (!read) { + RNP_LOG("premature end of armored input"); + return false; + } + + dptr = dend; + bptr = b64buf; + bend = b64buf + read; + /* checking input data, stripping away whitespaces, checking for end of the b64 data */ + while (bptr < bend) { + if ((bval = B64DEC[*(bptr++)]) < 64) { + *(dptr++) = bval; + } else if (bval == 0xfe) { + /* '=' means the base64 padding or the beginning of checksum */ + param->eofb64 = true; + break; + } else if (bval == 0xff) { + auto ch = *(bptr - 1); + /* OpenPGP message headers without the crc and without trailing = */ + if ((ch == CH_DASH) && !param->noheaders) { + param->eofb64 = true; + break; + } + RNP_LOG("wrong base64 character 0x%02hhX", ch); + return false; + } + } + + dend = dptr; + dptr = decbuf; + /* Processing full 4s which will go directly to the buf. + After this left < 3 or decbuf has < 4 bytes */ + if ((size_t)(dend - dptr) / 4 * 3 < left) { + pend = decbuf + (dend - dptr) / 4 * 4; + left -= (dend - dptr) / 4 * 3; + } else { + pend = decbuf + (left / 3) * 4; + left -= left / 3 * 3; + } + + /* this one would the most performance-consuming part for large chunks */ + while (dptr < pend) { + b24 = *dptr++ << 18; + b24 |= *dptr++ << 12; + b24 |= *dptr++ << 6; + b24 |= *dptr++; + *bufptr++ = b24 >> 16; + *bufptr++ = b24 >> 8; + *bufptr++ = b24 & 0xff; + } + + /* moving rest to the beginning of decbuf */ + memmove(decbuf, dptr, dend - dptr); + dend = decbuf + (dend - dptr); + + /* skip already processed data */ + if (!param->eofb64) { + /* all input is base64 data or eol/spaces, so skipping it */ + src_skip(param->readsrc, read); + /* check for eof for base64-encoded data without headers */ + if (param->noheaders && src_eof(param->readsrc)) { + src_skip(param->readsrc, read); + param->eofb64 = true; + } else { + continue; + } + } else { + /* '=' reached, bptr points on it */ + src_skip(param->readsrc, bptr - b64buf - 1); + } + + /* end of base64 data */ + if (param->noheaders) { + if (!base64_read_padding(param, &eqcount)) { + return false; + } + break; + } + /* reading b64 padding if any */ + if (!armor_read_padding(param, &eqcount)) { + RNP_LOG("wrong padding"); + return false; + } + /* reading crc */ + if (!armor_read_crc(src)) { + RNP_LOG("Warning: missing or malformed CRC line"); + } + /* reading armor trailing line */ + if (!armor_read_trailer(src)) { + RNP_LOG("wrong armor trailer"); + return false; + } + break; + } while (left >= 3); + + /* process bytes left in decbuf */ + + dptr = decbuf; + pend = decbuf + (dend - decbuf) / 4 * 4; + bptr = param->rest; + while (dptr < pend) { + b24 = *dptr++ << 18; + b24 |= *dptr++ << 12; + b24 |= *dptr++ << 6; + b24 |= *dptr++; + *bptr++ = b24 >> 16; + *bptr++ = b24 >> 8; + *bptr++ = b24 & 0xff; + } + + if (!armored_update_crc(param, buf, bufptr - (uint8_t *) buf)) { + return false; + } + + if (param->eofb64) { + if ((dend - dptr + eqcount) % 4 != 0) { + RNP_LOG("wrong b64 padding"); + return false; + } + + if (eqcount == 1) { + b24 = (*dptr << 10) | (*(dptr + 1) << 4) | (*(dptr + 2) >> 2); + *bptr++ = b24 >> 8; + *bptr++ = b24 & 0xff; + } else if (eqcount == 2) { + *bptr++ = (*dptr << 2) | (*(dptr + 1) >> 4); + } + + /* Calculate CRC after reading whole input stream */ + if (!armored_update_crc(param, param->rest, bptr - param->rest, true)) { + return false; + } + } else { + /* few bytes which do not fit to 4 boundary */ + for (int i = 0; i < dend - dptr; i++) { + param->brest[i] = *(dptr + i); + } + param->brestlen = dend - dptr; + } + + param->restlen = bptr - param->rest; + + /* check whether we have some bytes to add */ + if ((left > 0) && (param->restlen > 0)) { + read = left > param->restlen ? param->restlen : left; + memcpy(bufptr, param->rest, read); + if (!param->eofb64 && !armored_update_crc(param, bufptr, read)) { + return false; + } + left -= read; + param->restpos += read; + } + + *readres = len - left; + return true; +} + +static void +armored_src_close(pgp_source_t *src) +{ + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (param) { + free(param->armorhdr); + free(param->version); + free(param->comment); + free(param->hash); + free(param->charset); + delete param; + src->param = NULL; + } +} + +/** @brief finds armor header position in the buffer, returning beginning of header or NULL. + * hdrlen will contain the length of the header + **/ +static const char * +find_armor_header(const char *buf, size_t len, size_t *hdrlen) +{ + int st = -1; + + for (unsigned i = 0; i < len - 10; i++) { + if ((buf[i] == CH_DASH) && !strncmp(&buf[i + 1], ST_DASHES, 4)) { + st = i; + break; + } + } + + if (st < 0) { + return NULL; + } + + for (unsigned i = st + 5; i <= len - 5; i++) { + if ((buf[i] == CH_DASH) && !strncmp(&buf[i + 1], ST_DASHES, 4)) { + *hdrlen = i + 5 - st; + return &buf[st]; + } + } + + return NULL; +} + +static bool +str_equals(const char *str, size_t len, const char *another) +{ + size_t alen = strlen(another); + return (len == alen) && !memcmp(str, another, alen); +} + +static pgp_armored_msg_t +armor_str_to_data_type(const char *str, size_t len) +{ + if (!str) { + return PGP_ARMORED_UNKNOWN; + } + if (str_equals(str, len, "BEGIN PGP MESSAGE")) { + return PGP_ARMORED_MESSAGE; + } + if (str_equals(str, len, "BEGIN PGP PUBLIC KEY BLOCK") || + str_equals(str, len, "BEGIN PGP PUBLIC KEY")) { + return PGP_ARMORED_PUBLIC_KEY; + } + if (str_equals(str, len, "BEGIN PGP SECRET KEY BLOCK") || + str_equals(str, len, "BEGIN PGP SECRET KEY") || + str_equals(str, len, "BEGIN PGP PRIVATE KEY BLOCK") || + str_equals(str, len, "BEGIN PGP PRIVATE KEY")) { + return PGP_ARMORED_SECRET_KEY; + } + if (str_equals(str, len, "BEGIN PGP SIGNATURE")) { + return PGP_ARMORED_SIGNATURE; + } + if (str_equals(str, len, "BEGIN PGP SIGNED MESSAGE")) { + return PGP_ARMORED_CLEARTEXT; + } + return PGP_ARMORED_UNKNOWN; +} + +pgp_armored_msg_t +rnp_armor_guess_type(pgp_source_t *src) +{ + uint8_t ptag; + + if (!src_peek_eq(src, &ptag, 1)) { + return PGP_ARMORED_UNKNOWN; + } + + switch (get_packet_type(ptag)) { + case PGP_PKT_PK_SESSION_KEY: + case PGP_PKT_SK_SESSION_KEY: + case PGP_PKT_ONE_PASS_SIG: + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_COMPRESSED: + case PGP_PKT_LITDATA: + case PGP_PKT_MARKER: + return PGP_ARMORED_MESSAGE; + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_PUBLIC_SUBKEY: + return PGP_ARMORED_PUBLIC_KEY; + case PGP_PKT_SECRET_KEY: + case PGP_PKT_SECRET_SUBKEY: + return PGP_ARMORED_SECRET_KEY; + case PGP_PKT_SIGNATURE: + return PGP_ARMORED_SIGNATURE; + default: + return PGP_ARMORED_UNKNOWN; + } +} + +static pgp_armored_msg_t +rnp_armored_guess_type_by_readahead(pgp_source_t *src) +{ + if (!src->cache) { + return PGP_ARMORED_UNKNOWN; + } + + pgp_source_t armorsrc = {0}; + pgp_source_t memsrc = {0}; + size_t read; + // peek as much as the cache can take + bool cache_res = src_peek(src, NULL, sizeof(src->cache->buf), &read); + if (!cache_res || !read || + init_mem_src(&memsrc, + src->cache->buf + src->cache->pos, + src->cache->len - src->cache->pos, + false)) { + return PGP_ARMORED_UNKNOWN; + } + rnp_result_t res = init_armored_src(&armorsrc, &memsrc); + if (res) { + src_close(&memsrc); + RNP_LOG("failed to parse armored data"); + return PGP_ARMORED_UNKNOWN; + } + pgp_armored_msg_t guessed = rnp_armor_guess_type(&armorsrc); + src_close(&armorsrc); + src_close(&memsrc); + return guessed; +} + +pgp_armored_msg_t +rnp_armored_get_type(pgp_source_t *src) +{ + pgp_armored_msg_t guessed = rnp_armored_guess_type_by_readahead(src); + if (guessed != PGP_ARMORED_UNKNOWN) { + return guessed; + } + + char hdr[ARMORED_PEEK_BUF_SIZE]; + const char *armhdr; + size_t armhdrlen; + size_t read; + + if (!src_peek(src, hdr, sizeof(hdr), &read) || (read < 20)) { + return PGP_ARMORED_UNKNOWN; + } + if (!(armhdr = find_armor_header(hdr, read, &armhdrlen))) { + return PGP_ARMORED_UNKNOWN; + } + + return armor_str_to_data_type(armhdr + 5, armhdrlen - 10); +} + +static bool +armor_parse_header(pgp_source_t *src) +{ + char hdr[ARMORED_PEEK_BUF_SIZE]; + const char * armhdr; + size_t armhdrlen; + size_t read; + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (!src_peek(param->readsrc, hdr, sizeof(hdr), &read) || (read < 20)) { + return false; + } + + if (!(armhdr = find_armor_header(hdr, read, &armhdrlen))) { + RNP_LOG("no armor header"); + return false; + } + + /* if there are non-whitespaces before the armor header then issue warning */ + for (char *ch = hdr; ch < armhdr; ch++) { + if (B64DEC[(uint8_t) *ch] != 0xfd) { + RNP_LOG("extra data before the header line"); + break; + } + } + + param->type = armor_str_to_data_type(armhdr + 5, armhdrlen - 10); + if (param->type == PGP_ARMORED_UNKNOWN) { + RNP_LOG("unknown armor header"); + return false; + } + + if ((param->armorhdr = (char *) malloc(armhdrlen - 9)) == NULL) { + RNP_LOG("allocation failed"); + return false; + } + + memcpy(param->armorhdr, armhdr + 5, armhdrlen - 10); + param->armorhdr[armhdrlen - 10] = '\0'; + src_skip(param->readsrc, armhdr - hdr + armhdrlen); + armor_skip_chars(param->readsrc, "\t "); + return true; +} + +static bool +armor_skip_line(pgp_source_t *src) +{ + char header[ARMORED_PEEK_BUF_SIZE] = {0}; + do { + size_t hdrlen = 0; + bool res = src_peek_line(src, header, sizeof(header), &hdrlen); + if (hdrlen) { + src_skip(src, hdrlen); + } + if (res || (hdrlen < sizeof(header) - 1)) { + return res; + } + } while (1); +} + +static bool +is_base64_line(const char *line, size_t len) +{ + for (size_t i = 0; i < len && line[i]; i++) { + if (B64DEC[(uint8_t) line[i]] == 0xff) + return false; + } + return true; +} + +static bool +armor_parse_headers(pgp_source_t *src) +{ + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + char header[ARMORED_PEEK_BUF_SIZE] = {0}; + + do { + size_t hdrlen = 0; + if (!src_peek_line(param->readsrc, header, sizeof(header), &hdrlen)) { + /* if line is too long let's cut it to the reasonable size */ + src_skip(param->readsrc, hdrlen); + if ((hdrlen != sizeof(header) - 1) || !armor_skip_line(param->readsrc)) { + RNP_LOG("failed to peek line: unexpected end of data"); + return false; + } + RNP_LOG("Too long armor header - truncated."); + header[hdrlen] = '\0'; + } else if (hdrlen) { + if (is_base64_line(header, hdrlen)) { + RNP_LOG("Warning: no empty line after the base64 headers"); + return true; + } + src_skip(param->readsrc, hdrlen); + if (rnp::is_blank_line(header, hdrlen)) { + return src_skip_eol(param->readsrc); + } + } else { + /* empty line - end of the headers */ + return src_skip_eol(param->readsrc); + } + + char *hdrval = (char *) malloc(hdrlen + 1); + if (!hdrval) { + RNP_LOG("malloc failed"); + return false; + } + + if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_VERSION, 9)) { + memcpy(hdrval, header + 9, hdrlen - 8); + free(param->version); + param->version = hdrval; + } else if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_COMMENT, 9)) { + memcpy(hdrval, header + 9, hdrlen - 8); + free(param->comment); + param->comment = hdrval; + } else if ((hdrlen >= 5) && !strncmp(header, ST_HEADER_HASH, 6)) { + memcpy(hdrval, header + 6, hdrlen - 5); + free(param->hash); + param->hash = hdrval; + } else if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_CHARSET, 9)) { + memcpy(hdrval, header + 9, hdrlen - 8); + free(param->charset); + param->charset = hdrval; + } else { + RNP_LOG("unknown header '%s'", header); + free(hdrval); + } + + if (!src_skip_eol(param->readsrc)) { + return false; + } + } while (1); +} + +rnp_result_t +init_armored_src(pgp_source_t *src, pgp_source_t *readsrc, bool noheaders) +{ + if (!init_src_common(src, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_source_armored_param_t *param = new (std::nothrow) pgp_source_armored_param_t(); + if (!param) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + param->readsrc = readsrc; + param->noheaders = noheaders; + src->param = param; + src->read = armored_src_read; + src->close = armored_src_close; + src->type = PGP_STREAM_ARMORED; + + /* base64 data only */ + if (noheaders) { + return RNP_SUCCESS; + } + + /* initialize crc context */ + param->crc_ctx = rnp::CRC24::create(); + /* parsing armored header */ + rnp_result_t errcode = RNP_ERROR_GENERIC; + if (!armor_parse_header(src)) { + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + /* eol */ + if (!src_skip_eol(param->readsrc)) { + RNP_LOG("no eol after the armor header"); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + /* parsing headers */ + if (!armor_parse_headers(src)) { + RNP_LOG("failed to parse headers"); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + + /* now we are good to go with base64-encoded data */ + errcode = RNP_SUCCESS; +finish: + if (errcode) { + src_close(src); + } + return errcode; +} + +/** @brief Write message header to the dst. */ +static bool +armor_write_message_header(pgp_dest_armored_param_t *param, bool finish) +{ + const char *str = finish ? ST_ARMOR_END : ST_ARMOR_BEGIN; + dst_write(param->writedst, str, strlen(str)); + switch (param->type) { + case PGP_ARMORED_MESSAGE: + str = "MESSAGE"; + break; + case PGP_ARMORED_PUBLIC_KEY: + str = "PUBLIC KEY BLOCK"; + break; + case PGP_ARMORED_SECRET_KEY: + str = "PRIVATE KEY BLOCK"; + break; + case PGP_ARMORED_SIGNATURE: + str = "SIGNATURE"; + break; + case PGP_ARMORED_CLEARTEXT: + str = "SIGNED MESSAGE"; + break; + default: + return false; + } + dst_write(param->writedst, str, strlen(str)); + dst_write(param->writedst, ST_DASHES, strlen(ST_DASHES)); + return true; +} + +static void +armor_write_eol(pgp_dest_armored_param_t *param) +{ + if (param->eol[0]) { + dst_write(param->writedst, ¶m->eol[0], 1); + } + if (param->eol[1]) { + dst_write(param->writedst, ¶m->eol[1], 1); + } +} + +static void +armor_append_eol(pgp_dest_armored_param_t *param, uint8_t *&ptr) +{ + if (param->eol[0]) { + *ptr++ = param->eol[0]; + } + if (param->eol[1]) { + *ptr++ = param->eol[1]; + } +} + +/* Base 64 encoded table, quadruplicated to save cycles on use & 0x3f operation */ +static const uint8_t B64ENC[256] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', + '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/'}; + +static void +armored_encode3(uint8_t *out, uint8_t *in) +{ + out[0] = B64ENC[in[0] >> 2]; + out[1] = B64ENC[((in[0] << 4) | (in[1] >> 4)) & 0xff]; + out[2] = B64ENC[((in[1] << 2) | (in[2] >> 6)) & 0xff]; + out[3] = B64ENC[in[2] & 0xff]; +} + +static rnp_result_t +armored_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param; + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* update crc */ + bool base64 = param->type == PGP_ARMORED_BASE64; + if (!base64) { + try { + param->crc_ctx->add(buf, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + } + + uint8_t encbuf[PGP_INPUT_CACHE_SIZE / 2]; + uint8_t *bufptr = (uint8_t *) buf; + uint8_t *bufend = bufptr + len; + uint8_t *encptr = encbuf; + /* processing tail if any */ + if (len + param->tailc < 3) { + memcpy(¶m->tail[param->tailc], buf, len); + param->tailc += len; + return RNP_SUCCESS; + } else if (param->tailc > 0) { + uint8_t dec3[3] = {0}; + memcpy(dec3, param->tail, param->tailc); + memcpy(&dec3[param->tailc], bufptr, 3 - param->tailc); + bufptr += 3 - param->tailc; + param->tailc = 0; + armored_encode3(encptr, dec3); + encptr += 4; + param->lout += 4; + if (param->lout == param->llen) { + armor_append_eol(param, encptr); + param->lout = 0; + } + } + + /* this version prints whole chunks, so rounding down to the closest 4 */ + auto adjusted_llen = param->llen & ~3; + /* number of input bytes to form a whole line of output, param->llen / 4 * 3 */ + auto inllen = (adjusted_llen >> 2) + (adjusted_llen >> 1); + /* pointer to the last full line space in encbuf */ + auto enclast = encbuf + sizeof(encbuf) - adjusted_llen - 2; + + /* processing line chunks, this is the main performance-hitting cycle */ + while (bufptr + 3 <= bufend) { + /* checking whether we have enough space in encbuf */ + if (encptr > enclast) { + dst_write(param->writedst, encbuf, encptr - encbuf); + encptr = encbuf; + } + /* setup length of the input to process in this iteration */ + uint8_t *inlend = + !param->lout ? bufptr + inllen : bufptr + ((adjusted_llen - param->lout) >> 2) * 3; + if (inlend > bufend) { + /* no enough input for the full line */ + inlend = bufptr + (bufend - bufptr) / 3 * 3; + param->lout += (inlend - bufptr) / 3 * 4; + } else { + /* we have full line of input */ + param->lout = 0; + } + + /* processing one line */ + while (bufptr < inlend) { + uint32_t t = (bufptr[0] << 16) | (bufptr[1] << 8) | (bufptr[2]); + bufptr += 3; + *encptr++ = B64ENC[(t >> 18) & 0xff]; + *encptr++ = B64ENC[(t >> 12) & 0xff]; + *encptr++ = B64ENC[(t >> 6) & 0xff]; + *encptr++ = B64ENC[t & 0xff]; + } + + /* adding line ending */ + if (!param->lout) { + armor_append_eol(param, encptr); + } + } + + dst_write(param->writedst, encbuf, encptr - encbuf); + + /* saving tail */ + param->tailc = bufend - bufptr; + memcpy(param->tail, bufptr, param->tailc); + + return RNP_SUCCESS; +} + +static rnp_result_t +armored_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param; + + /* writing tail */ + uint8_t buf[5]; + if (param->tailc == 1) { + buf[0] = B64ENC[param->tail[0] >> 2]; + buf[1] = B64ENC[(param->tail[0] << 4) & 0xff]; + buf[2] = CH_EQ; + buf[3] = CH_EQ; + dst_write(param->writedst, buf, 4); + } else if (param->tailc == 2) { + buf[0] = B64ENC[(param->tail[0] >> 2)]; + buf[1] = B64ENC[((param->tail[0] << 4) | (param->tail[1] >> 4)) & 0xff]; + buf[2] = B64ENC[(param->tail[1] << 2) & 0xff]; + buf[3] = CH_EQ; + dst_write(param->writedst, buf, 4); + } + /* Check for base64 */ + if (param->type == PGP_ARMORED_BASE64) { + return param->writedst->werr; + } + + /* writing EOL if needed */ + if ((param->tailc > 0) || (param->lout > 0)) { + armor_write_eol(param); + } + + /* writing CRC and EOL */ + // At this point crc_ctx is initialized, so call can't fail + buf[0] = CH_EQ; + try { + auto crc = param->crc_ctx->finish(); + armored_encode3(&buf[1], crc.data()); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + dst_write(param->writedst, buf, 5); + armor_write_eol(param); + + /* writing armor header */ + if (!armor_write_message_header(param, true)) { + return RNP_ERROR_BAD_PARAMETERS; + } + armor_write_eol(param); + return param->writedst->werr; +} + +static void +armored_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param; + + if (!param) { + return; + } + /* dst_close may be called without dst_finish on error */ + delete param; + dst->param = NULL; +} + +rnp_result_t +init_armored_dst(pgp_dest_t *dst, pgp_dest_t *writedst, pgp_armored_msg_t msgtype) +{ + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_dest_armored_param_t *param = new (std::nothrow) pgp_dest_armored_param_t(); + if (!param) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->param = param; + dst->write = armored_dst_write; + dst->finish = armored_dst_finish; + dst->close = armored_dst_close; + dst->type = PGP_STREAM_ARMORED; + dst->writeb = 0; + dst->clen = 0; + + param->writedst = writedst; + param->type = msgtype; + /* Base64 message */ + if (msgtype == PGP_ARMORED_BASE64) { + /* Base64 encoding will not output EOLs but we need this to not duplicate code for a + * separate base64_dst_write function */ + param->eol[0] = 0; + param->eol[1] = 0; + param->llen = 256; + return RNP_SUCCESS; + } + /* create crc context */ + param->crc_ctx = rnp::CRC24::create(); + param->eol[0] = CH_CR; + param->eol[1] = CH_LF; + param->llen = 76; /* must be multiple of 4 */ + /* armor header */ + if (!armor_write_message_header(param, false)) { + RNP_LOG("unknown data type"); + armored_dst_close(dst, true); + return RNP_ERROR_BAD_PARAMETERS; + } + armor_write_eol(param); + /* empty line */ + armor_write_eol(param); + return RNP_SUCCESS; +} + +bool +is_armored_dest(pgp_dest_t *dst) +{ + return dst->type == PGP_STREAM_ARMORED; +} + +rnp_result_t +armored_dst_set_line_length(pgp_dest_t *dst, size_t llen) +{ + if (!dst || (llen < ARMORED_MIN_LINE_LENGTH) || (llen > ARMORED_MAX_LINE_LENGTH) || + !dst->param || !is_armored_dest(dst)) { + return RNP_ERROR_BAD_PARAMETERS; + } + auto param = (pgp_dest_armored_param_t *) dst->param; + param->llen = llen; + return RNP_SUCCESS; +} + +bool +is_armored_source(pgp_source_t *src) +{ + uint8_t buf[ARMORED_PEEK_BUF_SIZE]; + size_t read = 0; + + if (!src_peek(src, buf, sizeof(buf), &read) || (read < strlen(ST_ARMOR_BEGIN) + 1)) { + return false; + } + buf[read - 1] = 0; + return !!strstr((char *) buf, ST_ARMOR_BEGIN); +} + +bool +is_cleartext_source(pgp_source_t *src) +{ + uint8_t buf[ARMORED_PEEK_BUF_SIZE]; + size_t read = 0; + + if (!src_peek(src, buf, sizeof(buf), &read) || (read < strlen(ST_CLEAR_BEGIN))) { + return false; + } + buf[read - 1] = 0; + return !!strstr((char *) buf, ST_CLEAR_BEGIN); +} + +bool +is_base64_source(pgp_source_t &src) +{ + char buf[128]; + size_t read = 0; + + if (!src_peek(&src, buf, sizeof(buf), &read) || (read < 4)) { + return false; + } + return is_base64_line(buf, read); +} + +rnp_result_t +rnp_dearmor_source(pgp_source_t *src, pgp_dest_t *dst) +{ + rnp_result_t res = RNP_ERROR_BAD_FORMAT; + pgp_source_t armorsrc = {0}; + + /* initializing armored message */ + res = init_armored_src(&armorsrc, src); + if (res) { + return res; + } + /* Reading data from armored source and writing it to the output */ + res = dst_write_src(&armorsrc, dst); + if (res) { + RNP_LOG("dearmoring failed"); + } + + src_close(&armorsrc); + return res; +} + +rnp_result_t +rnp_armor_source(pgp_source_t *src, pgp_dest_t *dst, pgp_armored_msg_t msgtype) +{ + pgp_dest_t armordst = {0}; + rnp_result_t res = init_armored_dst(&armordst, dst, msgtype); + if (res) { + return res; + } + + res = dst_write_src(src, &armordst); + if (res) { + RNP_LOG("armoring failed"); + } + + dst_close(&armordst, res != RNP_SUCCESS); + return res; +} + +namespace rnp { + +const uint32_t ArmoredSource::AllowBinary = 0x01; +const uint32_t ArmoredSource::AllowBase64 = 0x02; +const uint32_t ArmoredSource::AllowMultiple = 0x04; + +ArmoredSource::ArmoredSource(pgp_source_t &readsrc, uint32_t flags) + : Source(), readsrc_(readsrc), multiple_(false) +{ + /* Do not dearmor already armored stream */ + bool already = readsrc_.type == PGP_STREAM_ARMORED; + /* Check for base64 source: no multiple streams allowed */ + if (!already && (flags & AllowBase64) && (is_base64_source(readsrc))) { + auto res = init_armored_src(&src_, &readsrc_, true); + if (res) { + RNP_LOG("Failed to parse base64 data."); + throw rnp::rnp_exception(res); + } + armored_ = true; + return; + } + /* Check for armored source */ + if (!already && is_armored_source(&readsrc)) { + auto res = init_armored_src(&src_, &readsrc_); + if (res) { + RNP_LOG("Failed to parse armored data."); + throw rnp::rnp_exception(res); + } + armored_ = true; + multiple_ = flags & AllowMultiple; + return; + } + /* Use binary source if allowed */ + if (!(flags & AllowBinary)) { + RNP_LOG("Non-armored data is not allowed here."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + armored_ = false; +} + +void +ArmoredSource::restart() +{ + if (!armored_ || src_eof(&readsrc_) || src_error(&readsrc_)) { + return; + } + src_close(&src_); + auto res = init_armored_src(&src_, &readsrc_); + if (res) { + throw rnp::rnp_exception(res); + } +} + +pgp_source_t & +ArmoredSource::src() +{ + return armored_ ? src_ : readsrc_; +} +} // namespace rnp diff --git a/src/librepgp/stream-armor.h b/src/librepgp/stream-armor.h new file mode 100644 index 0000000..4c91fd2 --- /dev/null +++ b/src/librepgp/stream-armor.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_ARMOUR_H_ +#define STREAM_ARMOUR_H_ + +#include "stream-common.h" + +typedef enum { + PGP_ARMORED_UNKNOWN, + PGP_ARMORED_MESSAGE, + PGP_ARMORED_PUBLIC_KEY, + PGP_ARMORED_SECRET_KEY, + PGP_ARMORED_SIGNATURE, + PGP_ARMORED_CLEARTEXT, + PGP_ARMORED_BASE64 +} pgp_armored_msg_t; + +/* @brief Init dearmoring stream + * @param src allocated pgp_source_t structure + * @param readsrc source to read data from + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t init_armored_src(pgp_source_t *src, + pgp_source_t *readsrc, + bool noheaders = false); + +/* @brief Init armoring stream + * @param dst allocated pgp_dest_t structure + * @param writedst destination to write armored data to + * @param msgtype type of the message (see pgp_armored_msg_t) + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t init_armored_dst(pgp_dest_t * dst, + pgp_dest_t * writedst, + pgp_armored_msg_t msgtype); + +/* @brief Dearmor the source, outputting binary data + * @param src initialized source with armored data + * @param dst initialized dest to write binary data to + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t rnp_dearmor_source(pgp_source_t *src, pgp_dest_t *dst); + +/* @brief Armor the source, outputting base64-encoded data with headers + * @param src initialized source with binary data + * @param dst destination to write armored data + * @msgtype type of the message, to write correct armor headers + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t rnp_armor_source(pgp_source_t *src, pgp_dest_t *dst, pgp_armored_msg_t msgtype); + +/* @brief Guess the corresponding armored message type by first byte(s) of PGP message + * @param src initialized source with binary PGP message data + * @return corresponding enum element or PGP_ARMORED_UNKNOWN + **/ +pgp_armored_msg_t rnp_armor_guess_type(pgp_source_t *src); + +/* @brief Get type of the armored message by peeking header. + * @param src initialized source with armored message data. + * @return corresponding enum element or PGP_ARMORED_UNKNOWN + **/ +pgp_armored_msg_t rnp_armored_get_type(pgp_source_t *src); + +/* @brief Check whether source could be an armored source + * @param src initialized source with some data + * @return true if source could be an armored data or false otherwise + **/ +bool is_armored_source(pgp_source_t *src); + +/* @brief Check whether destination is armored + * @param dest initialized destination + * @return true if destination is armored or false otherwise + **/ +bool is_armored_dest(pgp_dest_t *dst); + +/* @brief Check whether source is cleartext signed + * @param src initialized source with some data + * @return true if source could be a cleartext signed data or false otherwise + **/ +bool is_cleartext_source(pgp_source_t *src); + +/** @brief Check whether source is base64-encoded + * @param src initialized source with some data + * @return true if source could be a base64-encoded data or false otherwise + **/ +bool is_base64_source(pgp_source_t &src); + +/** Set line length for armoring + * + * @param dst initialized dest to write armored data to + * @param llen line length in characters + * @return RNP_SUCCESS on success, or any other value on error + */ +rnp_result_t armored_dst_set_line_length(pgp_dest_t *dst, size_t llen); + +namespace rnp { + +class ArmoredSource : public Source { + pgp_source_t &readsrc_; + bool armored_; + bool multiple_; + + public: + static const uint32_t AllowBinary; + static const uint32_t AllowBase64; + static const uint32_t AllowMultiple; + + ArmoredSource(const ArmoredSource &) = delete; + ArmoredSource(ArmoredSource &&) = delete; + + ArmoredSource(pgp_source_t &readsrc, uint32_t flags = 0); + + pgp_source_t &src(); + + bool + multiple() + { + return multiple_; + } + + /* Restart dearmoring in case of multiple armored messages in a single stream */ + void restart(); +}; + +class ArmoredDest : public Dest { + pgp_dest_t &writedst_; + + public: + ArmoredDest(const ArmoredDest &) = delete; + ArmoredDest(ArmoredDest &&) = delete; + + ArmoredDest(pgp_dest_t &writedst, pgp_armored_msg_t msgtype) : Dest(), writedst_(writedst) + { + auto ret = init_armored_dst(&dst_, &writedst_, msgtype); + if (ret) { + throw rnp::rnp_exception(ret); + } + }; + + ~ArmoredDest() + { + if (!discard_) { + dst_finish(&dst_); + } + } +}; + +} // namespace rnp + +#endif diff --git a/src/librepgp/stream-common.cpp b/src/librepgp/stream-common.cpp new file mode 100644 index 0000000..334f93b --- /dev/null +++ b/src/librepgp/stream-common.cpp @@ -0,0 +1,1212 @@ +/* + * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#include <string.h> +#else +#include "uniwin.h" +#endif +#include <sys/stat.h> +#include <stdarg.h> +#include <errno.h> +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#include <rnp/rnp_def.h> +#include "rnp.h" +#include "stream-common.h" +#include "types.h" +#include "file-utils.h" +#include "crypto/mem.h" +#include <algorithm> +#include <memory> + +bool +src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + size_t left = len; + size_t read; + pgp_source_cache_t *cache = src->cache; + bool readahead = cache ? cache->readahead : false; + + if (src->error) { + return false; + } + + if (src->eof || (len == 0)) { + *readres = 0; + return true; + } + + // Do not read more then available if source size is known + if (src->knownsize && (src->readb + len > src->size)) { + len = src->size - src->readb; + left = len; + readahead = false; + } + + // Check whether we have cache and there is data inside + if (cache && (cache->len > cache->pos)) { + read = cache->len - cache->pos; + if (read >= len) { + memcpy(buf, &cache->buf[cache->pos], len); + cache->pos += len; + goto finish; + } else { + memcpy(buf, &cache->buf[cache->pos], read); + cache->pos += read; + buf = (uint8_t *) buf + read; + left = len - read; + } + } + + // If we got here then we have empty cache or no cache at all + while (left > 0) { + if (left > sizeof(cache->buf) || !readahead || !cache) { + // If there is no cache or chunk is larger then read directly + if (!src->read(src, buf, left, &read)) { + src->error = 1; + return false; + } + if (!read) { + src->eof = 1; + len = len - left; + goto finish; + } + left -= read; + buf = (uint8_t *) buf + read; + } else { + // Try to fill the cache to avoid small reads + if (!src->read(src, &cache->buf[0], sizeof(cache->buf), &read)) { + src->error = 1; + return false; + } + if (!read) { + src->eof = 1; + len = len - left; + goto finish; + } else if (read < left) { + memcpy(buf, &cache->buf[0], read); + left -= read; + buf = (uint8_t *) buf + read; + } else { + memcpy(buf, &cache->buf[0], left); + cache->pos = left; + cache->len = read; + goto finish; + } + } + } + +finish: + src->readb += len; + if (src->knownsize && (src->readb == src->size)) { + src->eof = 1; + } + *readres = len; + return true; +} + +bool +src_read_eq(pgp_source_t *src, void *buf, size_t len) +{ + size_t res = 0; + return src_read(src, buf, len, &res) && (res == len); +} + +bool +src_peek(pgp_source_t *src, void *buf, size_t len, size_t *peeked) +{ + pgp_source_cache_t *cache = src->cache; + if (src->error) { + return false; + } + if (!cache || (len > sizeof(cache->buf))) { + return false; + } + if (src->eof) { + *peeked = 0; + return true; + } + + size_t read = 0; + bool readahead = cache->readahead; + // Do not read more then available if source size is known + if (src->knownsize && (src->readb + len > src->size)) { + len = src->size - src->readb; + readahead = false; + } + + if (cache->len - cache->pos >= len) { + if (buf) { + memcpy(buf, &cache->buf[cache->pos], len); + } + *peeked = len; + return true; + } + + if (cache->pos > 0) { + memmove(&cache->buf[0], &cache->buf[cache->pos], cache->len - cache->pos); + cache->len -= cache->pos; + cache->pos = 0; + } + + while (cache->len < len) { + read = readahead ? sizeof(cache->buf) - cache->len : len - cache->len; + if (src->knownsize && (src->readb + read > src->size)) { + read = src->size - src->readb; + } + if (!src->read(src, &cache->buf[cache->len], read, &read)) { + src->error = 1; + return false; + } + if (!read) { + if (buf) { + memcpy(buf, &cache->buf[0], cache->len); + } + *peeked = cache->len; + return true; + } + cache->len += read; + if (cache->len >= len) { + if (buf) { + memcpy(buf, cache->buf, len); + } + *peeked = len; + return true; + } + } + return false; +} + +bool +src_peek_eq(pgp_source_t *src, void *buf, size_t len) +{ + size_t res = 0; + return src_peek(src, buf, len, &res) && (res == len); +} + +void +src_skip(pgp_source_t *src, size_t len) +{ + if (src->cache && (src->cache->len - src->cache->pos >= len)) { + src->readb += len; + src->cache->pos += len; + return; + } + + size_t res = 0; + uint8_t sbuf[16]; + if (len < sizeof(sbuf)) { + (void) src_read(src, sbuf, len, &res); + return; + } + if (src_eof(src)) { + return; + } + + void *buf = calloc(1, std::min((size_t) PGP_INPUT_CACHE_SIZE, len)); + if (!buf) { + src->error = 1; + return; + } + + while (len && !src_eof(src)) { + if (!src_read(src, buf, std::min((size_t) PGP_INPUT_CACHE_SIZE, len), &res)) { + break; + } + len -= res; + } + free(buf); +} + +rnp_result_t +src_finish(pgp_source_t *src) +{ + rnp_result_t res = RNP_SUCCESS; + if (src->finish) { + res = src->finish(src); + } + + return res; +} + +bool +src_error(const pgp_source_t *src) +{ + return src->error; +} + +bool +src_eof(pgp_source_t *src) +{ + if (src->eof) { + return true; + } + /* Error on stream read is NOT considered as eof. See src_error(). */ + uint8_t check; + size_t read = 0; + return src_peek(src, &check, 1, &read) && (read == 0); +} + +void +src_close(pgp_source_t *src) +{ + if (src->close) { + src->close(src); + } + + if (src->cache) { + free(src->cache); + src->cache = NULL; + } +} + +bool +src_skip_eol(pgp_source_t *src) +{ + uint8_t eol[2]; + size_t read; + + if (!src_peek(src, eol, 2, &read) || !read) { + return false; + } + if (eol[0] == '\n') { + src_skip(src, 1); + return true; + } + if ((read == 2) && (eol[0] == '\r') && (eol[1] == '\n')) { + src_skip(src, 2); + return true; + } + return false; +} + +bool +src_peek_line(pgp_source_t *src, char *buf, size_t len, size_t *readres) +{ + size_t scan_pos = 0; + size_t inc = 64; + len = len - 1; + + do { + size_t to_peek = scan_pos + inc; + to_peek = to_peek > len ? len : to_peek; + inc = inc * 2; + + /* inefficient, each time we again read from the beginning */ + if (!src_peek(src, buf, to_peek, readres)) { + return false; + } + + /* we continue scanning where we stopped previously */ + for (; scan_pos < *readres; scan_pos++) { + if (buf[scan_pos] == '\n') { + if ((scan_pos > 0) && (buf[scan_pos - 1] == '\r')) { + scan_pos--; + } + buf[scan_pos] = '\0'; + *readres = scan_pos; + return true; + } + } + if (*readres < to_peek) { + return false; + } + } while (scan_pos < len); + return false; +} + +bool +init_src_common(pgp_source_t *src, size_t paramsize) +{ + memset(src, 0, sizeof(*src)); + src->cache = (pgp_source_cache_t *) calloc(1, sizeof(*src->cache)); + if (!src->cache) { + RNP_LOG("cache allocation failed"); + return false; + } + src->cache->readahead = true; + if (!paramsize) { + return true; + } + src->param = calloc(1, paramsize); + if (!src->param) { + RNP_LOG("param allocation failed"); + free(src->cache); + src->cache = NULL; + return false; + } + return true; +} + +typedef struct pgp_source_file_param_t { + int fd; +} pgp_source_file_param_t; + +static bool +file_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param; + if (!param) { + return false; + } + + int64_t rres = read(param->fd, buf, len); + if (rres < 0) { + return false; + } + *readres = rres; + return true; +} + +static void +file_src_close(pgp_source_t *src) +{ + pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param; + if (param) { + if (src->type == PGP_STREAM_FILE) { + close(param->fd); + } + free(src->param); + src->param = NULL; + } +} + +static rnp_result_t +init_fd_src(pgp_source_t *src, int fd, uint64_t *size) +{ + if (!init_src_common(src, sizeof(pgp_source_file_param_t))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param; + param->fd = fd; + src->read = file_src_read; + src->close = file_src_close; + src->type = PGP_STREAM_FILE; + src->size = size ? *size : 0; + src->knownsize = !!size; + + return RNP_SUCCESS; +} + +rnp_result_t +init_file_src(pgp_source_t *src, const char *path) +{ + int fd; + struct stat st; + + if (rnp_stat(path, &st) != 0) { + RNP_LOG("can't stat '%s'", path); + return RNP_ERROR_READ; + } + + /* read call may succeed on directory depending on OS type */ + if (S_ISDIR(st.st_mode)) { + RNP_LOG("source is directory"); + return RNP_ERROR_BAD_PARAMETERS; + } + + int flags = O_RDONLY; +#ifdef HAVE_O_BINARY + flags |= O_BINARY; +#else +#ifdef HAVE__O_BINARY + flags |= _O_BINARY; +#endif +#endif + fd = rnp_open(path, flags, 0); + + if (fd < 0) { + RNP_LOG("can't open '%s'", path); + return RNP_ERROR_READ; + } + uint64_t size = st.st_size; + rnp_result_t ret = init_fd_src(src, fd, &size); + if (ret) { + close(fd); + } + return ret; +} + +rnp_result_t +init_stdin_src(pgp_source_t *src) +{ + pgp_source_file_param_t *param; + + if (!init_src_common(src, sizeof(pgp_source_file_param_t))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_source_file_param_t *) src->param; + param->fd = 0; + src->read = file_src_read; + src->close = file_src_close; + src->type = PGP_STREAM_STDIN; + + return RNP_SUCCESS; +} + +typedef struct pgp_source_mem_param_t { + const void *memory; + bool free; + size_t len; + size_t pos; +} pgp_source_mem_param_t; + +typedef struct pgp_dest_mem_param_t { + unsigned maxalloc; + unsigned allocated; + void * memory; + bool free; + bool discard_overflow; + bool secure; +} pgp_dest_mem_param_t; + +static bool +mem_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + if (!param) { + return false; + } + + if (len > param->len - param->pos) { + len = param->len - param->pos; + } + memcpy(buf, (uint8_t *) param->memory + param->pos, len); + param->pos += len; + *read = len; + return true; +} + +static void +mem_src_close(pgp_source_t *src) +{ + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + if (param) { + if (param->free) { + free((void *) param->memory); + } + free(src->param); + src->param = NULL; + } +} + +rnp_result_t +init_mem_src(pgp_source_t *src, const void *mem, size_t len, bool free) +{ + if (!mem && len) { + return RNP_ERROR_NULL_POINTER; + } + /* this is actually double buffering, but then src_peek will fail */ + if (!init_src_common(src, sizeof(pgp_source_mem_param_t))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + param->memory = mem; + param->len = len; + param->pos = 0; + param->free = free; + src->read = mem_src_read; + src->close = mem_src_close; + src->finish = NULL; + src->size = len; + src->knownsize = 1; + src->type = PGP_STREAM_MEMORY; + + return RNP_SUCCESS; +} + +static bool +null_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + return false; +} + +rnp_result_t +init_null_src(pgp_source_t *src) +{ + memset(src, 0, sizeof(*src)); + src->read = null_src_read; + src->type = PGP_STREAM_NULL; + src->error = true; + return RNP_SUCCESS; +} + +rnp_result_t +read_mem_src(pgp_source_t *src, pgp_source_t *readsrc) +{ + pgp_dest_t dst; + rnp_result_t ret; + + if ((ret = init_mem_dest(&dst, NULL, 0))) { + return ret; + } + + if ((ret = dst_write_src(readsrc, &dst))) { + goto done; + } + + if ((ret = init_mem_src(src, mem_dest_own_memory(&dst), dst.writeb, true))) { + goto done; + } + + ret = RNP_SUCCESS; +done: + dst_close(&dst, true); + return ret; +} + +rnp_result_t +file_to_mem_src(pgp_source_t *src, const char *filename) +{ + pgp_source_t fsrc = {}; + rnp_result_t res = RNP_ERROR_GENERIC; + + if ((res = init_file_src(&fsrc, filename))) { + return res; + } + + res = read_mem_src(src, &fsrc); + src_close(&fsrc); + + return res; +} + +const void * +mem_src_get_memory(pgp_source_t *src, bool own) +{ + if (src->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return NULL; + } + + if (!src->param) { + return NULL; + } + + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + if (own) { + param->free = false; + } + return param->memory; +} + +bool +init_dst_common(pgp_dest_t *dst, size_t paramsize) +{ + memset(dst, 0, sizeof(*dst)); + dst->werr = RNP_SUCCESS; + if (!paramsize) { + return true; + } + /* allocate param */ + dst->param = calloc(1, paramsize); + if (!dst->param) { + RNP_LOG("allocation failed"); + } + return dst->param; +} + +void +dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + /* we call write function only if all previous calls succeeded */ + if ((len > 0) && (dst->write) && (dst->werr == RNP_SUCCESS)) { + /* if cache non-empty and len will overflow it then fill it and write out */ + if ((dst->clen > 0) && (dst->clen + len > sizeof(dst->cache))) { + memcpy(dst->cache + dst->clen, buf, sizeof(dst->cache) - dst->clen); + buf = (uint8_t *) buf + sizeof(dst->cache) - dst->clen; + len -= sizeof(dst->cache) - dst->clen; + dst->werr = dst->write(dst, dst->cache, sizeof(dst->cache)); + dst->writeb += sizeof(dst->cache); + dst->clen = 0; + if (dst->werr != RNP_SUCCESS) { + return; + } + } + + /* here everything will fit into the cache or cache is empty */ + if (dst->no_cache || (len > sizeof(dst->cache))) { + dst->werr = dst->write(dst, buf, len); + if (!dst->werr) { + dst->writeb += len; + } + } else { + memcpy(dst->cache + dst->clen, buf, len); + dst->clen += len; + } + } +} + +void +dst_printf(pgp_dest_t *dst, const char *format, ...) +{ + char buf[2048]; + size_t len; + va_list ap; + + va_start(ap, format); + len = vsnprintf(buf, sizeof(buf), format, ap); + va_end(ap); + + if (len >= sizeof(buf)) { + RNP_LOG("too long dst_printf"); + len = sizeof(buf) - 1; + } + dst_write(dst, buf, len); +} + +void +dst_flush(pgp_dest_t *dst) +{ + if ((dst->clen > 0) && (dst->write) && (dst->werr == RNP_SUCCESS)) { + dst->werr = dst->write(dst, dst->cache, dst->clen); + dst->writeb += dst->clen; + dst->clen = 0; + } +} + +rnp_result_t +dst_finish(pgp_dest_t *dst) +{ + rnp_result_t res = RNP_SUCCESS; + + if (!dst->finished) { + /* flush write cache in the dst */ + dst_flush(dst); + if (dst->finish) { + res = dst->finish(dst); + } + dst->finished = true; + } + + return res; +} + +void +dst_close(pgp_dest_t *dst, bool discard) +{ + if (!discard && !dst->finished) { + dst_finish(dst); + } + + if (dst->close) { + dst->close(dst, discard); + } +} + +typedef struct pgp_dest_file_param_t { + int fd; + int errcode; + bool overwrite; + std::string path; +} pgp_dest_file_param_t; + +static rnp_result_t +file_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* we assyme that blocking I/O is used so everything is written or error received */ + ssize_t ret = write(param->fd, buf, len); + if (ret < 0) { + param->errcode = errno; + RNP_LOG("write failed, error %d", param->errcode); + return RNP_ERROR_WRITE; + } else { + param->errcode = 0; + return RNP_SUCCESS; + } +} + +static void +file_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + if (!param) { + return; + } + + if (dst->type == PGP_STREAM_FILE) { + close(param->fd); + if (discard) { + rnp_unlink(param->path.c_str()); + } + } + + delete param; + dst->param = NULL; +} + +static rnp_result_t +init_fd_dest(pgp_dest_t *dst, int fd, const char *path) +{ + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + try { + std::unique_ptr<pgp_dest_file_param_t> param(new pgp_dest_file_param_t()); + param->path = path; + param->fd = fd; + dst->param = param.release(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->write = file_dst_write; + dst->close = file_dst_close; + dst->type = PGP_STREAM_FILE; + return RNP_SUCCESS; +} + +rnp_result_t +init_file_dest(pgp_dest_t *dst, const char *path, bool overwrite) +{ + /* check whether file/dir already exists */ + struct stat st; + if (!rnp_stat(path, &st)) { + if (!overwrite) { + RNP_LOG("file already exists: '%s'", path); + return RNP_ERROR_WRITE; + } + + /* if we are overwriting empty directory then should first remove it */ + if (S_ISDIR(st.st_mode)) { + if (rmdir(path) == -1) { + RNP_LOG("failed to remove directory: error %d", errno); + return RNP_ERROR_BAD_PARAMETERS; + } + } + } + + int flags = O_WRONLY | O_CREAT; + flags |= overwrite ? O_TRUNC : O_EXCL; +#ifdef HAVE_O_BINARY + flags |= O_BINARY; +#else +#ifdef HAVE__O_BINARY + flags |= _O_BINARY; +#endif +#endif + int fd = rnp_open(path, flags, S_IRUSR | S_IWUSR); + if (fd < 0) { + RNP_LOG("failed to create file '%s'. Error %d.", path, errno); + return RNP_ERROR_WRITE; + } + + rnp_result_t res = init_fd_dest(dst, fd, path); + if (res) { + close(fd); + } + return res; +} + +#define TMPDST_SUFFIX ".rnp-tmp.XXXXXX" + +static rnp_result_t +file_tmpdst_finish(pgp_dest_t *dst) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + if (!param) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* close the file */ + close(param->fd); + param->fd = -1; + + /* rename the temporary file */ + if (param->path.size() < strlen(TMPDST_SUFFIX)) { + return RNP_ERROR_BAD_PARAMETERS; + } + try { + /* remove suffix so we have required path */ + std::string origpath(param->path.begin(), param->path.end() - strlen(TMPDST_SUFFIX)); + /* check if file already exists */ + struct stat st; + if (!rnp_stat(origpath.c_str(), &st)) { + if (!param->overwrite) { + RNP_LOG("target path already exists"); + return RNP_ERROR_BAD_STATE; + } +#ifdef _WIN32 + /* rename() call on Windows fails if destination exists */ + else { + rnp_unlink(origpath.c_str()); + } +#endif + + /* we should remove dir if overwriting, file will be unlinked in rename call */ + if (S_ISDIR(st.st_mode) && rmdir(origpath.c_str())) { + RNP_LOG("failed to remove directory"); + return RNP_ERROR_BAD_STATE; + } + } + + if (rnp_rename(param->path.c_str(), origpath.c_str())) { + RNP_LOG("failed to rename temporary path to target file: %s", strerror(errno)); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } +} + +static void +file_tmpdst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + if (!param) { + return; + } + + /* we close file in finish function, except the case when some error occurred */ + if (!dst->finished && (dst->type == PGP_STREAM_FILE)) { + close(param->fd); + if (discard) { + rnp_unlink(param->path.c_str()); + } + } + + delete param; + dst->param = NULL; +} + +rnp_result_t +init_tmpfile_dest(pgp_dest_t *dst, const char *path, bool overwrite) +{ + try { + std::string tmp = std::string(path) + std::string(TMPDST_SUFFIX); + /* make sure tmp.data() is zero-terminated */ + tmp.push_back('\0'); +#if defined(HAVE_MKSTEMP) && !defined(_WIN32) + int fd = mkstemp(&tmp[0]); +#else + int fd = rnp_mkstemp(&tmp[0]); +#endif + if (fd < 0) { + RNP_LOG("failed to create temporary file with template '%s'. Error %d.", + tmp.c_str(), + errno); + return RNP_ERROR_WRITE; + } + rnp_result_t res = init_fd_dest(dst, fd, tmp.c_str()); + if (res) { + close(fd); + return res; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + + /* now let's change some parameters to handle temporary file correctly */ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + param->overwrite = overwrite; + dst->finish = file_tmpdst_finish; + dst->close = file_tmpdst_close; + return RNP_SUCCESS; +} + +rnp_result_t +init_stdout_dest(pgp_dest_t *dst) +{ + rnp_result_t res = init_fd_dest(dst, STDOUT_FILENO, ""); + if (res) { + return res; + } + dst->type = PGP_STREAM_STDOUT; + return RNP_SUCCESS; +} + +static rnp_result_t +mem_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (!param) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* checking whether we need to realloc or discard extra bytes */ + if (param->discard_overflow && (dst->writeb >= param->allocated)) { + return RNP_SUCCESS; + } + if (param->discard_overflow && (dst->writeb + len > param->allocated)) { + len = param->allocated - dst->writeb; + } + + if (dst->writeb + len > param->allocated) { + if ((param->maxalloc > 0) && (dst->writeb + len > param->maxalloc)) { + RNP_LOG("attempt to alloc more then allowed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + /* round up to the page boundary and do it exponentially */ + size_t alloc = ((dst->writeb + len) * 2 + 4095) / 4096 * 4096; + if ((param->maxalloc > 0) && (alloc > param->maxalloc)) { + alloc = param->maxalloc; + } + + void *newalloc = param->secure ? calloc(1, alloc) : realloc(param->memory, alloc); + if (!newalloc) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (param->secure && param->memory) { + memcpy(newalloc, param->memory, dst->writeb); + secure_clear(param->memory, dst->writeb); + free(param->memory); + } + param->memory = newalloc; + param->allocated = alloc; + } + + memcpy((uint8_t *) param->memory + dst->writeb, buf, len); + return RNP_SUCCESS; +} + +static void +mem_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (!param) { + return; + } + + if (param->free) { + if (param->secure) { + secure_clear(param->memory, param->allocated); + } + free(param->memory); + } + free(param); + dst->param = NULL; +} + +rnp_result_t +init_mem_dest(pgp_dest_t *dst, void *mem, unsigned len) +{ + pgp_dest_mem_param_t *param; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_mem_param_t *) dst->param; + + param->maxalloc = len; + param->allocated = mem ? len : 0; + param->memory = mem; + param->free = !mem; + param->secure = false; + + dst->write = mem_dst_write; + dst->close = mem_dst_close; + dst->type = PGP_STREAM_MEMORY; + dst->werr = RNP_SUCCESS; + dst->no_cache = true; + + return RNP_SUCCESS; +} + +void +mem_dest_discard_overflow(pgp_dest_t *dst, bool discard) +{ + if (dst->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return; + } + + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (param) { + param->discard_overflow = discard; + } +} + +void * +mem_dest_get_memory(pgp_dest_t *dst) +{ + if (dst->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return NULL; + } + + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + + if (param) { + return param->memory; + } + + return NULL; +} + +void * +mem_dest_own_memory(pgp_dest_t *dst) +{ + if (dst->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return NULL; + } + + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + + if (!param) { + RNP_LOG("null param"); + return NULL; + } + + dst_finish(dst); + + if (param->free) { + if (!dst->writeb) { + free(param->memory); + param->memory = NULL; + return param->memory; + } + /* it may be larger then required - let's truncate */ + void *newalloc = realloc(param->memory, dst->writeb); + if (!newalloc) { + return NULL; + } + param->memory = newalloc; + param->allocated = dst->writeb; + param->free = false; + return param->memory; + } + + /* in this case we should copy the memory */ + void *res = malloc(dst->writeb); + if (res) { + memcpy(res, param->memory, dst->writeb); + } + return res; +} + +void +mem_dest_secure_memory(pgp_dest_t *dst, bool secure) +{ + if (!dst || (dst->type != PGP_STREAM_MEMORY)) { + RNP_LOG("wrong function call"); + return; + } + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (param) { + param->secure = secure; + } +} + +static rnp_result_t +null_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + return RNP_SUCCESS; +} + +static void +null_dst_close(pgp_dest_t *dst, bool discard) +{ + ; +} + +rnp_result_t +init_null_dest(pgp_dest_t *dst) +{ + dst->param = NULL; + dst->write = null_dst_write; + dst->close = null_dst_close; + dst->type = PGP_STREAM_NULL; + dst->writeb = 0; + dst->clen = 0; + dst->werr = RNP_SUCCESS; + dst->no_cache = true; + + return RNP_SUCCESS; +} + +rnp_result_t +dst_write_src(pgp_source_t *src, pgp_dest_t *dst, uint64_t limit) +{ + const size_t bufsize = PGP_INPUT_CACHE_SIZE; + uint8_t * readbuf = (uint8_t *) malloc(bufsize); + if (!readbuf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t res = RNP_SUCCESS; + try { + size_t read; + uint64_t totalread = 0; + + while (!src->eof) { + if (!src_read(src, readbuf, bufsize, &read)) { + res = RNP_ERROR_GENERIC; + break; + } + if (!read) { + continue; + } + totalread += read; + if (limit && totalread > limit) { + res = RNP_ERROR_GENERIC; + break; + } + if (dst) { + dst_write(dst, readbuf, read); + if (dst->werr) { + RNP_LOG("failed to output data"); + res = RNP_ERROR_WRITE; + break; + } + } + } + } catch (...) { + free(readbuf); + throw; + } + free(readbuf); + if (res || !dst) { + return res; + } + dst_flush(dst); + return dst->werr; +} diff --git a/src/librepgp/stream-common.h b/src/librepgp/stream-common.h new file mode 100644 index 0000000..02279d3 --- /dev/null +++ b/src/librepgp/stream-common.h @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_COMMON_H_ +#define STREAM_COMMON_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "types.h" + +#define PGP_INPUT_CACHE_SIZE 32768 +#define PGP_OUTPUT_CACHE_SIZE 32768 + +#define PGP_PARTIAL_PKT_FIRST_PART_MIN_SIZE 512 + +typedef enum { + PGP_STREAM_NULL, + PGP_STREAM_FILE, + PGP_STREAM_MEMORY, + PGP_STREAM_STDIN, + PGP_STREAM_STDOUT, + PGP_STREAM_PACKET, + PGP_STREAM_PARLEN_PACKET, + PGP_STREAM_LITERAL, + PGP_STREAM_COMPRESSED, + PGP_STREAM_ENCRYPTED, + PGP_STREAM_SIGNED, + PGP_STREAM_ARMORED, + PGP_STREAM_CLEARTEXT +} pgp_stream_type_t; + +typedef struct pgp_source_t pgp_source_t; +typedef struct pgp_dest_t pgp_dest_t; + +typedef bool pgp_source_read_func_t(pgp_source_t *src, void *buf, size_t len, size_t *read); +typedef rnp_result_t pgp_source_finish_func_t(pgp_source_t *src); +typedef void pgp_source_close_func_t(pgp_source_t *src); + +typedef rnp_result_t pgp_dest_write_func_t(pgp_dest_t *dst, const void *buf, size_t len); +typedef rnp_result_t pgp_dest_finish_func_t(pgp_dest_t *src); +typedef void pgp_dest_close_func_t(pgp_dest_t *dst, bool discard); + +/* statically preallocated cache for sources */ +typedef struct pgp_source_cache_t { + uint8_t buf[PGP_INPUT_CACHE_SIZE]; + unsigned pos; /* current position in cache */ + unsigned len; /* number of bytes available in cache */ + bool readahead; /* whether read-ahead with larger chunks allowed */ +} pgp_source_cache_t; + +typedef struct pgp_source_t { + pgp_source_read_func_t * read; + pgp_source_finish_func_t *finish; + pgp_source_close_func_t * close; + pgp_stream_type_t type; + + uint64_t size; /* size of the data if available, see knownsize */ + uint64_t readb; /* number of bytes read from the stream via src_read. Do not confuse with + number of bytes as returned via the read since data may be cached */ + pgp_source_cache_t *cache; /* cache if used */ + void * param; /* source-specific additional data */ + + unsigned eof : 1; /* end of data as reported by read and empty cache */ + unsigned knownsize : 1; /* whether size of the data is known */ + unsigned error : 1; /* there were reading error */ +} pgp_source_t; + +/** @brief helper function to allocate memory for source's cache and param + * Also fills src and param with zeroes + * @param src pointer to the source structure + * @param paramsize number of bytes required for src->param + * @return true on success or false if memory allocation failed. + **/ +bool init_src_common(pgp_source_t *src, size_t paramsize); + +/** @brief read up to len bytes from the source + * While this function tries to read as much bytes as possible however it may return + * less then len bytes. Then src->eof can be checked if it's end of data. + * + * @param src source structure + * @param buf preallocated buffer which can store up to len bytes + * @param len number of bytes to read + * @param read number of read bytes will be stored here. Cannot be NULL. + * @return true on success or false otherwise + **/ +bool src_read(pgp_source_t *src, void *buf, size_t len, size_t *read); + +/** @brief shortcut to read exactly len bytes from source. See src_read for parameters. + * @return true if len bytes were read or false otherwise (i.e. less then len were read or + * read error occurred) */ +bool src_read_eq(pgp_source_t *src, void *buf, size_t len); + +/** @brief read up to len bytes and keep them in the cache/do not process + * Works only for streams with cache + * @param src source structure + * @param buf preallocated buffer which can store up to len bytes, or NULL if data should be + * discarded, just making sure that needed input is available in source + * @param len number of bytes to read. Must be less then PGP_INPUT_CACHE_SIZE. + * @param read number of bytes read will be stored here. Cannot be NULL. + * @return true on success or false otherwise + **/ +bool src_peek(pgp_source_t *src, void *buf, size_t len, size_t *read); + +/** @brief shortcut to read exactly len bytes and keep them in the cache/do not process + * Works only for streams with cache + * @return true if len bytes were read or false otherwise (i.e. less then len were read or + * read error occurred) */ +bool src_peek_eq(pgp_source_t *src, void *buf, size_t len); + +/** @brief skip up to len bytes. + * Note: use src_read() if you want to check error condition/get number of bytes + *skipped. + * @param src source structure + * @param len number of bytes to skip + **/ +void src_skip(pgp_source_t *src, size_t len); + +/** @brief notify source that all reading is done, so final data processing may be started, + * i.e. signature reading and verification and so on. Do not misuse with src_close. + * @param src allocated and initialized source structure + * @return RNP_SUCCESS or error code. If source doesn't have finish handler then also + * RNP_SUCCESS is returned + */ +rnp_result_t src_finish(pgp_source_t *src); + +/** @brief check whether there were reading error on source + * @param allocated and initialized source structure + * @return true if there were reading error or false otherwise + */ +bool src_error(const pgp_source_t *src); + +/** @brief check whether there is no more input on source + * @param src allocated and initialized source structure + * @return true if there is no more input or false otherwise. + * On read error false will be returned. + */ +bool src_eof(pgp_source_t *src); + +/** @brief close the source and deallocate all internal resources if any + */ +void src_close(pgp_source_t *src); + +/** @brief skip end of line on the source (\r\n or \n, depending on input) + * @param src allocated and initialized source + * @return true if eol was found and skipped or false otherwise + */ +bool src_skip_eol(pgp_source_t *src); + +/** @brief peek the line on the source + * @param src allocated and initialized source with data + * @param buf preallocated buffer to store the result. Result include NULL character and + * doesn't include the end of line sequence. + * @param len maximum length of data to store in buf, including terminating NULL + * @param read on success here will be stored number of bytes in the string, without the NULL + * character. + * @return true on success + * false is returned if there were eof, read error or eol was not found within the + * len. Supported eol sequences are \r\n and \n + */ +bool src_peek_line(pgp_source_t *src, char *buf, size_t len, size_t *read); + +/** @brief init file source + * @param src pre-allocated source structure + * @param path path to the file + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_file_src(pgp_source_t *src, const char *path); + +/** @brief init stdin source + * @param src pre-allocated source structure + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_stdin_src(pgp_source_t *src); + +/** @brief init memory source + * @param src pre-allocated source structure + * @param mem memory to read from + * @param len number of bytes in input + * @param free free the memory pointer on stream close or not + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_mem_src(pgp_source_t *src, const void *mem, size_t len, bool free); + +/** @brief init NULL source, which doesn't allow to read anything and always returns an error. + * @param src pre-allocated source structure + * @return always RNP_SUCCESS + **/ +rnp_result_t init_null_src(pgp_source_t *src); + +/** @brief init memory source with contents of other source + * @param src pre-allocated source structure + * @param readsrc opened source with data + * @return RNP_SUCCESS or error code + **/ +rnp_result_t read_mem_src(pgp_source_t *src, pgp_source_t *readsrc); + +/** @brief init memory source with contents of the specified file + * @param src pre-allocated source structure + * @param filename name of the file + * @return RNP_SUCCESS or error code + **/ +rnp_result_t file_to_mem_src(pgp_source_t *src, const char *filename); + +/** @brief get memory from the memory source + * @param src initialized memory source + * @param own transfer ownership of the memory + * @return pointer to the memory or NULL if it is not a memory source + **/ +const void *mem_src_get_memory(pgp_source_t *src, bool own = false); + +typedef struct pgp_dest_t { + pgp_dest_write_func_t * write; + pgp_dest_finish_func_t *finish; + pgp_dest_close_func_t * close; + pgp_stream_type_t type; + rnp_result_t werr; /* write function may set this to some error code */ + + size_t writeb; /* number of bytes written */ + void * param; /* source-specific additional data */ + bool no_cache; /* disable write caching */ + uint8_t cache[PGP_OUTPUT_CACHE_SIZE]; + unsigned clen; /* number of bytes in cache */ + bool finished; /* whether dst_finish was called on dest or not */ +} pgp_dest_t; + +/** @brief helper function to allocate memory for dest's param. + * Initializes dst and param with zeroes as well. + * @param dst dest structure + * @param paramsize number of bytes required for dst->param + * @return true on success, or false if memory allocation failed + **/ +bool init_dst_common(pgp_dest_t *dst, size_t paramsize); + +/** @brief write buffer to the destination + * + * @param dst destination structure + * @param buf buffer with data + * @param len number of bytes to write + * @return true on success or false otherwise + **/ +void dst_write(pgp_dest_t *dst, const void *buf, size_t len); + +/** @brief printf formatted string to the destination + * + * @param dst destination structure + * @param format format string, which is the same as printf() uses + * @param ... additional arguments + */ +void dst_printf(pgp_dest_t *dst, const char *format, ...); + +/** @brief do all finalization tasks after all writing is done, i.e. calculate and write + * mdc, signatures and so on. Do not misuse with dst_close. If was not called then will be + * called from the dst_close + * + * @param dst destination structure + * @return RNP_SUCCESS or error code if something went wrong + **/ +rnp_result_t dst_finish(pgp_dest_t *dst); + +/** @brief close the destination + * + * @param dst destination structure to be closed + * @param discard if this is true then all produced output should be discarded + * @return void + **/ +void dst_close(pgp_dest_t *dst, bool discard); + +/** @brief flush cached data if any. dst_write caches small writes, so data does not + * immediately go to stream write function. + * + * @param dst destination structure + * @return void + **/ +void dst_flush(pgp_dest_t *dst); + +/** @brief init file destination + * @param dst pre-allocated dest structure + * @param path path to the file + * @param overwrite overwrite existing file + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_file_dest(pgp_dest_t *dst, const char *path, bool overwrite); + +/** @brief init file destination, using the temporary file name, based on path. + * Once writing is over, dst_finish() will attempt to rename to the desired name. + * @param dst pre-allocated dest structure + * @param path path to the file + * @param overwrite overwrite existing file on rename + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_tmpfile_dest(pgp_dest_t *dst, const char *path, bool overwrite); + +/** @brief init stdout destination + * @param dst pre-allocated dest structure + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_stdout_dest(pgp_dest_t *dst); + +/** @brief init memory destination + * @param dst pre-allocated dest structure + * @param mem pointer to the pre-allocated memory buffer, or NULL if it should be allocated + * @param len number of bytes which mem can keep, or maximum amount of memory to allocate if + * mem is NULL. If len is zero in later case then allocation is not limited. + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_mem_dest(pgp_dest_t *dst, void *mem, unsigned len); + +/** @brief set whether to silently discard bytes which overflow memory of the dst. + * @param dst pre-allocated and initialized memory dest + * @param discard true to discard or false to return an error on overflow. + **/ +void mem_dest_discard_overflow(pgp_dest_t *dst, bool discard); + +/** @brief get the pointer to the memory where data is written. + * Do not retain the result, it may change between calls due to realloc + * @param dst pre-allocated and initialized memory dest + * @return pointer to the memory area or NULL if memory was not allocated + **/ +void *mem_dest_get_memory(pgp_dest_t *dst); + +/** @brief get ownership on the memory dest's contents. This must be called only before + * closing the dest + * @param dst pre-allocated and initialized memory dest + * @return pointer to the memory area or NULL if memory was not allocated (i.e. nothing was + * written to the destination). Also NULL will be returned on possible (re-)allocation + * failure, this case can be identified by non-zero dst->writeb. + **/ +void *mem_dest_own_memory(pgp_dest_t *dst); + +/** @brief mark memory dest as secure, so it will be deallocated securely + * @param dst pre-allocated and initialized memory dest + * @param secure whether memory should be considered as secure or not + * @return void + **/ +void mem_dest_secure_memory(pgp_dest_t *dst, bool secure); + +/** @brief init null destination which silently discards all the output + * @param dst pre-allocated dest structure + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_null_dest(pgp_dest_t *dst); + +/** @brief reads from source and writes to destination + * @param src initialized source + * @param dst initialized destination + * @param limit sets the maximum amount of bytes to be read, + * returning an error if the source hasn't come to eof after that amount + * if 0, no limit is imposed + * @return RNP_SUCCESS or error code + **/ +rnp_result_t dst_write_src(pgp_source_t *src, pgp_dest_t *dst, uint64_t limit = 0); + +namespace rnp { +/* Temporary wrapper to destruct stack-based pgp_source_t */ +class Source { + protected: + pgp_source_t src_; + + public: + Source(const Source &) = delete; + Source(Source &&) = delete; + + Source() : src_({}) + { + } + + virtual ~Source() + { + src_close(&src_); + } + + virtual pgp_source_t & + src() + { + return src_; + } + + size_t + size() + { + return src().size; + } + + size_t + readb() + { + return src().readb; + } + + bool + eof() + { + return src_eof(&src()); + } + + bool + error() + { + return src_error(&src()); + } +}; + +class MemorySource : public Source { + public: + MemorySource(const MemorySource &) = delete; + MemorySource(MemorySource &&) = delete; + + /** + * @brief Construct memory source object. + * + * @param mem source memory. Must be valid for the whole lifetime of the object. + * @param len size of the memory. + * @param free free memory once processing is finished. + */ + MemorySource(const void *mem, size_t len, bool free) : Source() + { + auto res = init_mem_src(&src_, mem, len, free); + if (res) { + throw std::bad_alloc(); + } + } + + /** + * @brief Construct memory source object + * + * @param vec vector with data. Must be valid for the whole lifetime of the object. + */ + MemorySource(const std::vector<uint8_t> &vec) : MemorySource(vec.data(), vec.size(), false) + { + } + + MemorySource(pgp_source_t &src) : Source() + { + auto res = read_mem_src(&src_, &src); + if (res) { + throw rnp::rnp_exception(res); + } + } + + const void * + memory(bool own = false) + { + return mem_src_get_memory(&src_, own); + } +}; + +/* Temporary wrapper to destruct stack-based pgp_dest_t */ +class Dest { + protected: + pgp_dest_t dst_; + bool discard_; + + public: + Dest(const Dest &) = delete; + Dest(Dest &&) = delete; + + Dest() : dst_({}), discard_(false) + { + } + + virtual ~Dest() + { + dst_close(&dst_, discard_); + } + + void + write(const void *buf, size_t len) + { + dst_write(&dst_, buf, len); + } + + void + set_discard(bool discard) + { + discard_ = discard; + } + + pgp_dest_t & + dst() + { + return dst_; + } + + size_t + writeb() + { + return dst_.writeb; + } + + rnp_result_t + werr() + { + return dst_.werr; + } +}; + +class MemoryDest : public Dest { + public: + MemoryDest(const MemoryDest &) = delete; + MemoryDest(MemoryDest &&) = delete; + + MemoryDest(void *mem = NULL, size_t len = 0) : Dest() + { + auto res = init_mem_dest(&dst_, mem, len); + if (res) { + throw std::bad_alloc(); + } + discard_ = true; + } + + void * + memory() + { + return mem_dest_get_memory(&dst_); + } + + void + set_secure(bool secure) + { + mem_dest_secure_memory(&dst_, secure); + } + + std::vector<uint8_t> + to_vector() + { + uint8_t *mem = (uint8_t *) memory(); + return std::vector<uint8_t>(mem, mem + writeb()); + } +}; +} // namespace rnp + +#endif diff --git a/src/librepgp/stream-ctx.cpp b/src/librepgp/stream-ctx.cpp new file mode 100644 index 0000000..28b5444 --- /dev/null +++ b/src/librepgp/stream-ctx.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <assert.h> +#include "defaults.h" +#include "utils.h" +#include "stream-ctx.h" + +rnp_result_t +rnp_ctx_t::add_encryption_password(const std::string &password, + pgp_hash_alg_t halg, + pgp_symm_alg_t ealg, + size_t iterations) +{ + rnp_symmetric_pass_info_t info = {}; + + info.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED; + info.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED; + info.s2k.hash_alg = halg; + ctx->rng.get(info.s2k.salt, sizeof(info.s2k.salt)); + if (!iterations) { + iterations = ctx->s2k_iterations(halg); + } + if (!iterations) { + return RNP_ERROR_BAD_PARAMETERS; + } + info.s2k.iterations = pgp_s2k_encode_iterations(iterations); + info.s2k_cipher = ealg; + /* Note: we're relying on the fact that a longer-than-needed key length + * here does not change the entire derived key (it just generates unused + * extra bytes at the end). We derive a key of our maximum supported length, + * which is a bit wasteful. + * + * This is done because we do not yet know what cipher this key will actually + * end up being used with until later. + * + * An alternative would be to keep a list of actual passwords and s2k params, + * and save the key derivation for later. + */ + if (!pgp_s2k_derive_key(&info.s2k, password.c_str(), info.key.data(), info.key.size())) { + return RNP_ERROR_GENERIC; + } + passwords.push_back(info); + return RNP_SUCCESS; +} diff --git a/src/librepgp/stream-ctx.h b/src/librepgp/stream-ctx.h new file mode 100644 index 0000000..b9e0c10 --- /dev/null +++ b/src/librepgp/stream-ctx.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2019-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_CTX_H_ +#define STREAM_CTX_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "types.h" +#include <string> +#include <list> +#include "pgp-key.h" +#include "crypto/mem.h" +#include "sec_profile.hpp" + +/* signature info structure */ +typedef struct rnp_signer_info_t { + pgp_key_t * key{}; + pgp_hash_alg_t halg{}; + int64_t sigcreate{}; + uint64_t sigexpire{}; +} rnp_signer_info_t; + +typedef struct rnp_symmetric_pass_info_t { + pgp_s2k_t s2k{}; + pgp_symm_alg_t s2k_cipher{}; + + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> key; +} rnp_symmetric_pass_info_t; + +/** rnp operation context : contains configuration data about the currently ongoing operation. + * + * Common fields which make sense for every operation: + * - overwrite : silently overwrite output file if exists + * - armor : except cleartext signing, which outputs text in clear and always armor signature, + * this controls whether output is armored (base64-encoded). For armor/dearmor operation it + * controls the direction of the conversion (true means enarmor, false - dearmor), + * - rng : random number generator + * - operation : current operation type + * + * For operations with OpenPGP embedded data (i.e. encrypted data and attached signatures): + * - filename, filemtime : to specify information about the contents of literal data packet + * - zalg, zlevel : compression algorithm and level, zlevel = 0 to disable compression + * + * For encryption operation (including encrypt-and-sign): + * - halg : hash algorithm used during key derivation for password-based encryption + * - ealg, aalg, abits : symmetric encryption algorithm and AEAD parameters if used + * - recipients : list of key ids used to encrypt data to + * - passwords : list of passwords used for password-based encryption + * - filename, filemtime, zalg, zlevel : see previous + * + * For signing of any kind (attached, detached, cleartext): + * - clearsign, detached : controls kind of the signed data. Both are mutually-exclusive. + * If both are false then attached signing is used. + * - halg : hash algorithm used to calculate signature(s) + * - signers : list of rnp_signer_info_t structures describing signing key and parameters + * - sigcreate, sigexpire : default signature(s) creation and expiration times + * - filename, filemtime, zalg, zlevel : only for attached signatures, see previous + * + * For data decryption and/or verification there is not much of fields: + * - discard: discard the output data (i.e. just decrypt and/or verify signatures) + * + */ + +typedef struct rnp_ctx_t { + std::string filename{}; /* name of the input file to store in literal data packet */ + int64_t filemtime{}; /* file modification time to store in literal data packet */ + int64_t sigcreate{}; /* signature creation time */ + uint64_t sigexpire{}; /* signature expiration time */ + bool clearsign{}; /* cleartext signature */ + bool detached{}; /* detached signature */ + pgp_hash_alg_t halg{}; /* hash algorithm */ + pgp_symm_alg_t ealg{}; /* encryption algorithm */ + int zalg{}; /* compression algorithm used */ + int zlevel{}; /* compression level */ + pgp_aead_alg_t aalg{}; /* non-zero to use AEAD */ + int abits{}; /* AEAD chunk bits */ + bool overwrite{}; /* allow to overwrite output file if exists */ + bool armor{}; /* whether to use ASCII armor on output */ + bool no_wrap{}; /* do not wrap source in literal data packet */ + std::list<pgp_key_t *> recipients{}; /* recipients of the encrypted message */ + std::list<rnp_symmetric_pass_info_t> passwords{}; /* passwords to encrypt message */ + std::list<rnp_signer_info_t> signers{}; /* keys to which sign message */ + rnp::SecurityContext * ctx{}; /* pointer to rnp::RNG */ + + rnp_ctx_t() = default; + rnp_ctx_t(const rnp_ctx_t &) = delete; + rnp_ctx_t(rnp_ctx_t &&) = delete; + + rnp_ctx_t &operator=(const rnp_ctx_t &) = delete; + rnp_ctx_t &operator=(rnp_ctx_t &&) = delete; + + rnp_result_t add_encryption_password(const std::string &password, + pgp_hash_alg_t halg, + pgp_symm_alg_t ealg, + size_t iterations = 0); +} rnp_ctx_t; + +#endif diff --git a/src/librepgp/stream-def.h b/src/librepgp/stream-def.h new file mode 100644 index 0000000..7a1108f --- /dev/null +++ b/src/librepgp/stream-def.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_DEF_H_ +#define STREAM_DEF_H_ + +#define CT_BUF_LEN 4096 +#define CH_CR ('\r') +#define CH_LF ('\n') +#define CH_EQ ('=') +#define CH_DASH ('-') +#define CH_SPACE (' ') +#define CH_TAB ('\t') +#define CH_COMMA (',') +#define ST_CR ("\r") +#define ST_LF ("\n") +#define ST_CRLF ("\r\n") +#define ST_CRLFCRLF ("\r\n\r\n") +#define ST_DASHSP ("- ") +#define ST_COMMA (",") + +#define ST_DASHES ("-----") +#define ST_ARMOR_BEGIN ("-----BEGIN PGP ") +#define ST_ARMOR_END ("-----END PGP ") +#define ST_CLEAR_BEGIN ("-----BEGIN PGP SIGNED MESSAGE-----") +#define ST_SIG_BEGIN ("\n-----BEGIN PGP SIGNATURE-----") +#define ST_HEADER_VERSION ("Version: ") +#define ST_HEADER_COMMENT ("Comment: ") +#define ST_HEADER_HASH ("Hash: ") +#define ST_HEADER_CHARSET ("Charset: ") +#define ST_FROM ("From") + +/* Preallocated cache length for AEAD encryption/decryption */ +#define PGP_AEAD_CACHE_LEN (PGP_INPUT_CACHE_SIZE + PGP_AEAD_MAX_TAG_LEN) + +/* Maximum OpenPGP packet nesting level */ +#define MAXIMUM_NESTING_LEVEL 32 +#define MAXIMUM_STREAM_PKTS 16 +#define MAXIMUM_ERROR_PKTS 64 + +/* Maximum text line length supported by GnuPG */ +#define MAXIMUM_GNUPG_LINELEN 19995 + +#endif /* !STREAM_DEF_H_ */ diff --git a/src/librepgp/stream-dump.cpp b/src/librepgp/stream-dump.cpp new file mode 100644 index 0000000..416f9ae --- /dev/null +++ b/src/librepgp/stream-dump.cpp @@ -0,0 +1,2533 @@ +/* + * Copyright (c) 2018-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include "time-utils.h" +#include "stream-def.h" +#include "stream-dump.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "stream-parse.h" +#include "types.h" +#include "ctype.h" +#include "crypto/symmetric.h" +#include "crypto/s2k.h" +#include "fingerprint.h" +#include "pgp-key.h" +#include "crypto.h" +#include "json-utils.h" +#include <algorithm> + +static const id_str_pair packet_tag_map[] = { + {PGP_PKT_RESERVED, "Reserved"}, + {PGP_PKT_PK_SESSION_KEY, "Public-Key Encrypted Session Key"}, + {PGP_PKT_SIGNATURE, "Signature"}, + {PGP_PKT_SK_SESSION_KEY, "Symmetric-Key Encrypted Session Key"}, + {PGP_PKT_ONE_PASS_SIG, "One-Pass Signature"}, + {PGP_PKT_SECRET_KEY, "Secret Key"}, + {PGP_PKT_PUBLIC_KEY, "Public Key"}, + {PGP_PKT_SECRET_SUBKEY, "Secret Subkey"}, + {PGP_PKT_COMPRESSED, "Compressed Data"}, + {PGP_PKT_SE_DATA, "Symmetrically Encrypted Data"}, + {PGP_PKT_MARKER, "Marker"}, + {PGP_PKT_LITDATA, "Literal Data"}, + {PGP_PKT_TRUST, "Trust"}, + {PGP_PKT_USER_ID, "User ID"}, + {PGP_PKT_PUBLIC_SUBKEY, "Public Subkey"}, + {PGP_PKT_RESERVED2, "reserved2"}, + {PGP_PKT_RESERVED3, "reserved3"}, + {PGP_PKT_USER_ATTR, "User Attribute"}, + {PGP_PKT_SE_IP_DATA, "Symmetric Encrypted and Integrity Protected Data"}, + {PGP_PKT_MDC, "Modification Detection Code"}, + {PGP_PKT_AEAD_ENCRYPTED, "AEAD Encrypted Data Packet"}, + {0x00, NULL}, +}; + +static const id_str_pair sig_type_map[] = { + {PGP_SIG_BINARY, "Signature of a binary document"}, + {PGP_SIG_TEXT, "Signature of a canonical text document"}, + {PGP_SIG_STANDALONE, "Standalone signature"}, + {PGP_CERT_GENERIC, "Generic User ID certification"}, + {PGP_CERT_PERSONA, "Personal User ID certification"}, + {PGP_CERT_CASUAL, "Casual User ID certification"}, + {PGP_CERT_POSITIVE, "Positive User ID certification"}, + {PGP_SIG_SUBKEY, "Subkey Binding Signature"}, + {PGP_SIG_PRIMARY, "Primary Key Binding Signature"}, + {PGP_SIG_DIRECT, "Direct-key signature"}, + {PGP_SIG_REV_KEY, "Key revocation signature"}, + {PGP_SIG_REV_SUBKEY, "Subkey revocation signature"}, + {PGP_SIG_REV_CERT, "Certification revocation signature"}, + {PGP_SIG_TIMESTAMP, "Timestamp signature"}, + {PGP_SIG_3RD_PARTY, "Third-Party Confirmation signature"}, + {0x00, NULL}, +}; + +static const id_str_pair sig_subpkt_type_map[] = { + {PGP_SIG_SUBPKT_CREATION_TIME, "signature creation time"}, + {PGP_SIG_SUBPKT_EXPIRATION_TIME, "signature expiration time"}, + {PGP_SIG_SUBPKT_EXPORT_CERT, "exportable certification"}, + {PGP_SIG_SUBPKT_TRUST, "trust signature"}, + {PGP_SIG_SUBPKT_REGEXP, "regular expression"}, + {PGP_SIG_SUBPKT_REVOCABLE, "revocable"}, + {PGP_SIG_SUBPKT_KEY_EXPIRY, "key expiration time"}, + {PGP_SIG_SUBPKT_PREFERRED_SKA, "preferred symmetric algorithms"}, + {PGP_SIG_SUBPKT_REVOCATION_KEY, "revocation key"}, + {PGP_SIG_SUBPKT_ISSUER_KEY_ID, "issuer key ID"}, + {PGP_SIG_SUBPKT_NOTATION_DATA, "notation data"}, + {PGP_SIG_SUBPKT_PREFERRED_HASH, "preferred hash algorithms"}, + {PGP_SIG_SUBPKT_PREF_COMPRESS, "preferred compression algorithms"}, + {PGP_SIG_SUBPKT_KEYSERV_PREFS, "key server preferences"}, + {PGP_SIG_SUBPKT_PREF_KEYSERV, "preferred key server"}, + {PGP_SIG_SUBPKT_PRIMARY_USER_ID, "primary user ID"}, + {PGP_SIG_SUBPKT_POLICY_URI, "policy URI"}, + {PGP_SIG_SUBPKT_KEY_FLAGS, "key flags"}, + {PGP_SIG_SUBPKT_SIGNERS_USER_ID, "signer's user ID"}, + {PGP_SIG_SUBPKT_REVOCATION_REASON, "reason for revocation"}, + {PGP_SIG_SUBPKT_FEATURES, "features"}, + {PGP_SIG_SUBPKT_SIGNATURE_TARGET, "signature target"}, + {PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE, "embedded signature"}, + {PGP_SIG_SUBPKT_ISSUER_FPR, "issuer fingerprint"}, + {PGP_SIG_SUBPKT_PREFERRED_AEAD, "preferred AEAD algorithms"}, + {0x00, NULL}, +}; + +static const id_str_pair key_type_map[] = { + {PGP_PKT_SECRET_KEY, "Secret key"}, + {PGP_PKT_PUBLIC_KEY, "Public key"}, + {PGP_PKT_SECRET_SUBKEY, "Secret subkey"}, + {PGP_PKT_PUBLIC_SUBKEY, "Public subkey"}, + {0x00, NULL}, +}; + +static const id_str_pair pubkey_alg_map[] = { + {PGP_PKA_RSA, "RSA (Encrypt or Sign)"}, + {PGP_PKA_RSA_ENCRYPT_ONLY, "RSA (Encrypt-Only)"}, + {PGP_PKA_RSA_SIGN_ONLY, "RSA (Sign-Only)"}, + {PGP_PKA_ELGAMAL, "Elgamal (Encrypt-Only)"}, + {PGP_PKA_DSA, "DSA"}, + {PGP_PKA_ECDH, "ECDH"}, + {PGP_PKA_ECDSA, "ECDSA"}, + {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, "Elgamal"}, + {PGP_PKA_RESERVED_DH, "Reserved for DH (X9.42)"}, + {PGP_PKA_EDDSA, "EdDSA"}, + {PGP_PKA_SM2, "SM2"}, + {0x00, NULL}, +}; + +static const id_str_pair symm_alg_map[] = { + {PGP_SA_PLAINTEXT, "Plaintext"}, + {PGP_SA_IDEA, "IDEA"}, + {PGP_SA_TRIPLEDES, "TripleDES"}, + {PGP_SA_CAST5, "CAST5"}, + {PGP_SA_BLOWFISH, "Blowfish"}, + {PGP_SA_AES_128, "AES-128"}, + {PGP_SA_AES_192, "AES-192"}, + {PGP_SA_AES_256, "AES-256"}, + {PGP_SA_TWOFISH, "Twofish"}, + {PGP_SA_CAMELLIA_128, "Camellia-128"}, + {PGP_SA_CAMELLIA_192, "Camellia-192"}, + {PGP_SA_CAMELLIA_256, "Camellia-256"}, + {PGP_SA_SM4, "SM4"}, + {0x00, NULL}, +}; + +static const id_str_pair hash_alg_map[] = { + {PGP_HASH_MD5, "MD5"}, + {PGP_HASH_SHA1, "SHA1"}, + {PGP_HASH_RIPEMD, "RIPEMD160"}, + {PGP_HASH_SHA256, "SHA256"}, + {PGP_HASH_SHA384, "SHA384"}, + {PGP_HASH_SHA512, "SHA512"}, + {PGP_HASH_SHA224, "SHA224"}, + {PGP_HASH_SM3, "SM3"}, + {PGP_HASH_SHA3_256, "SHA3-256"}, + {PGP_HASH_SHA3_512, "SHA3-512"}, + {0x00, NULL}, +}; + +static const id_str_pair z_alg_map[] = { + {PGP_C_NONE, "Uncompressed"}, + {PGP_C_ZIP, "ZIP"}, + {PGP_C_ZLIB, "ZLib"}, + {PGP_C_BZIP2, "BZip2"}, + {0x00, NULL}, +}; + +static const id_str_pair aead_alg_map[] = { + {PGP_AEAD_NONE, "None"}, + {PGP_AEAD_EAX, "EAX"}, + {PGP_AEAD_OCB, "OCB"}, + {0x00, NULL}, +}; + +static const id_str_pair revoc_reason_map[] = { + {PGP_REVOCATION_NO_REASON, "No reason"}, + {PGP_REVOCATION_SUPERSEDED, "Superseded"}, + {PGP_REVOCATION_COMPROMISED, "Compromised"}, + {PGP_REVOCATION_RETIRED, "Retired"}, + {PGP_REVOCATION_NO_LONGER_VALID, "No longer valid"}, + {0x00, NULL}, +}; + +typedef struct pgp_dest_indent_param_t { + int level; + bool lstart; + pgp_dest_t *writedst; +} pgp_dest_indent_param_t; + +static rnp_result_t +indent_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + const char * line = (const char *) buf; + char indent[4] = {' ', ' ', ' ', ' '}; + + if (!len) { + return RNP_SUCCESS; + } + + do { + if (param->lstart) { + for (int i = 0; i < param->level; i++) { + dst_write(param->writedst, indent, sizeof(indent)); + } + param->lstart = false; + } + + for (size_t i = 0; i < len; i++) { + if ((line[i] == '\n') || (i == len - 1)) { + dst_write(param->writedst, line, i + 1); + param->lstart = line[i] == '\n'; + line += i + 1; + len -= i + 1; + break; + } + } + } while (len > 0); + + return RNP_SUCCESS; +} + +static void +indent_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + if (!param) { + return; + } + + free(param); +} + +static rnp_result_t +init_indent_dest(pgp_dest_t *dst, pgp_dest_t *origdst) +{ + pgp_dest_indent_param_t *param; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->write = indent_dst_write; + dst->close = indent_dst_close; + dst->finish = NULL; + dst->no_cache = true; + param = (pgp_dest_indent_param_t *) dst->param; + param->writedst = origdst; + param->lstart = true; + + return RNP_SUCCESS; +} + +static void +indent_dest_increase(pgp_dest_t *dst) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + param->level++; +} + +static void +indent_dest_decrease(pgp_dest_t *dst) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + if (param->level > 0) { + param->level--; + } +} + +static void +indent_dest_set(pgp_dest_t *dst, int level) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + param->level = level; +} + +static size_t +vsnprinthex(char *str, size_t slen, const uint8_t *buf, size_t buflen) +{ + static const char *hexes = "0123456789abcdef"; + size_t idx = 0; + + for (size_t i = 0; (i < buflen) && (i < (slen - 1) / 2); i++) { + str[idx++] = hexes[buf[i] >> 4]; + str[idx++] = hexes[buf[i] & 0xf]; + } + str[idx] = '\0'; + return buflen * 2; +} + +static void +dst_print_mpi(pgp_dest_t *dst, const char *name, pgp_mpi_t *mpi, bool dumpbin) +{ + char hex[5000]; + if (!dumpbin) { + dst_printf(dst, "%s: %d bits\n", name, (int) mpi_bits(mpi)); + } else { + vsnprinthex(hex, sizeof(hex), mpi->mpi, mpi->len); + dst_printf(dst, "%s: %d bits, %s\n", name, (int) mpi_bits(mpi), hex); + } +} + +static void +dst_print_palg(pgp_dest_t *dst, const char *name, pgp_pubkey_alg_t palg) +{ + const char *palg_name = id_str_pair::lookup(pubkey_alg_map, palg, "Unknown"); + if (!name) { + name = "public key algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) palg, palg_name); +} + +static void +dst_print_halg(pgp_dest_t *dst, const char *name, pgp_hash_alg_t halg) +{ + const char *halg_name = id_str_pair::lookup(hash_alg_map, halg, "Unknown"); + if (!name) { + name = "hash algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) halg, halg_name); +} + +static void +dst_print_salg(pgp_dest_t *dst, const char *name, pgp_symm_alg_t salg) +{ + const char *salg_name = id_str_pair::lookup(symm_alg_map, salg, "Unknown"); + if (!name) { + name = "symmetric algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) salg, salg_name); +} + +static void +dst_print_aalg(pgp_dest_t *dst, const char *name, pgp_aead_alg_t aalg) +{ + const char *aalg_name = id_str_pair::lookup(aead_alg_map, aalg, "Unknown"); + if (!name) { + name = "aead algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) aalg, aalg_name); +} + +static void +dst_print_zalg(pgp_dest_t *dst, const char *name, pgp_compression_type_t zalg) +{ + const char *zalg_name = id_str_pair::lookup(z_alg_map, zalg, "Unknown"); + if (!name) { + name = "compression algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) zalg, zalg_name); +} + +static void +dst_print_raw(pgp_dest_t *dst, const char *name, const void *data, size_t len) +{ + dst_printf(dst, "%s: ", name); + dst_write(dst, data, len); + dst_printf(dst, "\n"); +} + +static void +dst_print_algs( + pgp_dest_t *dst, const char *name, uint8_t *algs, size_t algc, const id_str_pair map[]) +{ + if (!name) { + name = "algorithms"; + } + + dst_printf(dst, "%s: ", name); + for (size_t i = 0; i < algc; i++) { + dst_printf( + dst, "%s%s", id_str_pair::lookup(map, algs[i], "Unknown"), i + 1 < algc ? ", " : ""); + } + dst_printf(dst, " ("); + for (size_t i = 0; i < algc; i++) { + dst_printf(dst, "%d%s", (int) algs[i], i + 1 < algc ? ", " : ""); + } + dst_printf(dst, ")\n"); +} + +static void +dst_print_sig_type(pgp_dest_t *dst, const char *name, pgp_sig_type_t sigtype) +{ + const char *sig_name = id_str_pair::lookup(sig_type_map, sigtype, "Unknown"); + if (!name) { + name = "signature type"; + } + dst_printf(dst, "%s: %d (%s)\n", name, (int) sigtype, sig_name); +} + +static void +dst_print_hex(pgp_dest_t *dst, const char *name, const uint8_t *data, size_t len, bool bytes) +{ + char hex[512]; + vsnprinthex(hex, sizeof(hex), data, len); + if (bytes) { + dst_printf(dst, "%s: 0x%s (%d bytes)\n", name, hex, (int) len); + } else { + dst_printf(dst, "%s: 0x%s\n", name, hex); + } +} + +static void +dst_print_keyid(pgp_dest_t *dst, const char *name, const pgp_key_id_t &keyid) +{ + if (!name) { + name = "key id"; + } + dst_print_hex(dst, name, keyid.data(), keyid.size(), false); +} + +static void +dst_print_s2k(pgp_dest_t *dst, pgp_s2k_t *s2k) +{ + dst_printf(dst, "s2k specifier: %d\n", (int) s2k->specifier); + if ((s2k->specifier == PGP_S2KS_EXPERIMENTAL) && s2k->gpg_ext_num) { + dst_printf(dst, "GPG extension num: %d\n", (int) s2k->gpg_ext_num); + if (s2k->gpg_ext_num == PGP_S2K_GPG_SMARTCARD) { + static_assert(sizeof(s2k->gpg_serial) == 16, "invalid s2k->gpg_serial size"); + size_t slen = s2k->gpg_serial_len > 16 ? 16 : s2k->gpg_serial_len; + dst_print_hex(dst, "card serial number", s2k->gpg_serial, slen, true); + } + return; + } + if (s2k->specifier == PGP_S2KS_EXPERIMENTAL) { + dst_print_hex(dst, + "Unknown experimental s2k", + s2k->experimental.data(), + s2k->experimental.size(), + true); + return; + } + dst_print_halg(dst, "s2k hash algorithm", s2k->hash_alg); + if ((s2k->specifier == PGP_S2KS_SALTED) || + (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED)) { + dst_print_hex(dst, "s2k salt", s2k->salt, PGP_SALT_SIZE, false); + } + if (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED) { + size_t real_iter = pgp_s2k_decode_iterations(s2k->iterations); + dst_printf(dst, "s2k iterations: %zu (encoded as %u)\n", real_iter, s2k->iterations); + } +} + +static void +dst_print_time(pgp_dest_t *dst, const char *name, uint32_t time) +{ + if (!name) { + name = "time"; + } + auto str = rnp_ctime(time).substr(0, 24); + dst_printf(dst, + "%s: %zu (%s%s)\n", + name, + (size_t) time, + rnp_y2k38_warning(time) ? ">=" : "", + str.c_str()); +} + +static void +dst_print_expiration(pgp_dest_t *dst, const char *name, uint32_t seconds) +{ + if (!name) { + name = "expiration"; + } + if (seconds) { + int days = seconds / (24 * 60 * 60); + dst_printf(dst, "%s: %zu seconds (%d days)\n", name, (size_t) seconds, days); + } else { + dst_printf(dst, "%s: 0 (never)\n", name); + } +} + +#define LINELEN 16 + +static void +dst_hexdump(pgp_dest_t *dst, const uint8_t *src, size_t length) +{ + size_t i; + char line[LINELEN + 1]; + + for (i = 0; i < length; i++) { + if (i % LINELEN == 0) { + dst_printf(dst, "%.5zu | ", i); + } + dst_printf(dst, "%.02x ", (uint8_t) src[i]); + line[i % LINELEN] = (isprint(src[i])) ? src[i] : '.'; + if (i % LINELEN == LINELEN - 1) { + line[LINELEN] = 0x0; + dst_printf(dst, " | %s\n", line); + } + } + if (i % LINELEN != 0) { + for (; i % LINELEN != 0; i++) { + dst_printf(dst, " "); + line[i % LINELEN] = ' '; + } + line[LINELEN] = 0x0; + dst_printf(dst, " | %s\n", line); + } +} + +static rnp_result_t stream_dump_packets_raw(rnp_dump_ctx_t *ctx, + pgp_source_t * src, + pgp_dest_t * dst); +static void stream_dump_signature_pkt(rnp_dump_ctx_t * ctx, + pgp_signature_t *sig, + pgp_dest_t * dst); + +static void +signature_dump_subpacket(rnp_dump_ctx_t *ctx, pgp_dest_t *dst, const pgp_sig_subpkt_t &subpkt) +{ + const char *sname = id_str_pair::lookup(sig_subpkt_type_map, subpkt.type, "Unknown"); + + switch (subpkt.type) { + case PGP_SIG_SUBPKT_CREATION_TIME: + dst_print_time(dst, sname, subpkt.fields.create); + break; + case PGP_SIG_SUBPKT_EXPIRATION_TIME: + dst_print_expiration(dst, sname, subpkt.fields.expiry); + break; + case PGP_SIG_SUBPKT_EXPORT_CERT: + dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.exportable); + break; + case PGP_SIG_SUBPKT_TRUST: + dst_printf(dst, + "%s: amount %d, level %d\n", + sname, + (int) subpkt.fields.trust.amount, + (int) subpkt.fields.trust.level); + break; + case PGP_SIG_SUBPKT_REGEXP: + dst_print_raw(dst, sname, subpkt.fields.regexp.str, subpkt.fields.regexp.len); + break; + case PGP_SIG_SUBPKT_REVOCABLE: + dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.revocable); + break; + case PGP_SIG_SUBPKT_KEY_EXPIRY: + dst_print_expiration(dst, sname, subpkt.fields.expiry); + break; + case PGP_SIG_SUBPKT_PREFERRED_SKA: + dst_print_algs(dst, + "preferred symmetric algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + symm_alg_map); + break; + case PGP_SIG_SUBPKT_REVOCATION_KEY: + dst_printf(dst, "%s\n", sname); + dst_printf(dst, "class: %d\n", (int) subpkt.fields.revocation_key.klass); + dst_print_palg(dst, NULL, subpkt.fields.revocation_key.pkalg); + dst_print_hex( + dst, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_SIZE, true); + break; + case PGP_SIG_SUBPKT_ISSUER_KEY_ID: + dst_print_hex(dst, sname, subpkt.fields.issuer, PGP_KEY_ID_SIZE, false); + break; + case PGP_SIG_SUBPKT_NOTATION_DATA: { + std::string name(subpkt.fields.notation.name, + subpkt.fields.notation.name + subpkt.fields.notation.nlen); + std::vector<uint8_t> value(subpkt.fields.notation.value, + subpkt.fields.notation.value + subpkt.fields.notation.vlen); + if (subpkt.fields.notation.human) { + dst_printf(dst, "%s: %s = ", sname, name.c_str()); + dst_printf(dst, "%.*s\n", (int) value.size(), (char *) value.data()); + } else { + char hex[64]; + vsnprinthex(hex, sizeof(hex), value.data(), value.size()); + dst_printf(dst, "%s: %s = ", sname, name.c_str()); + dst_printf(dst, "0x%s (%zu bytes)\n", hex, value.size()); + } + break; + } + case PGP_SIG_SUBPKT_PREFERRED_HASH: + dst_print_algs(dst, + "preferred hash algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + hash_alg_map); + break; + case PGP_SIG_SUBPKT_PREF_COMPRESS: + dst_print_algs(dst, + "preferred compression algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + z_alg_map); + break; + case PGP_SIG_SUBPKT_KEYSERV_PREFS: + dst_printf(dst, "%s\n", sname); + dst_printf(dst, "no-modify: %d\n", (int) subpkt.fields.ks_prefs.no_modify); + break; + case PGP_SIG_SUBPKT_PREF_KEYSERV: + dst_print_raw( + dst, sname, subpkt.fields.preferred_ks.uri, subpkt.fields.preferred_ks.len); + break; + case PGP_SIG_SUBPKT_PRIMARY_USER_ID: + dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.primary_uid); + break; + case PGP_SIG_SUBPKT_POLICY_URI: + dst_print_raw(dst, sname, subpkt.fields.policy.uri, subpkt.fields.policy.len); + break; + case PGP_SIG_SUBPKT_KEY_FLAGS: { + uint8_t flg = subpkt.fields.key_flags; + dst_printf(dst, "%s: 0x%02x ( ", sname, flg); + dst_printf(dst, "%s", flg ? "" : "none"); + dst_printf(dst, "%s", flg & PGP_KF_CERTIFY ? "certify " : ""); + dst_printf(dst, "%s", flg & PGP_KF_SIGN ? "sign " : ""); + dst_printf(dst, "%s", flg & PGP_KF_ENCRYPT_COMMS ? "encrypt_comm " : ""); + dst_printf(dst, "%s", flg & PGP_KF_ENCRYPT_STORAGE ? "encrypt_storage " : ""); + dst_printf(dst, "%s", flg & PGP_KF_SPLIT ? "split " : ""); + dst_printf(dst, "%s", flg & PGP_KF_AUTH ? "auth " : ""); + dst_printf(dst, "%s", flg & PGP_KF_SHARED ? "shared " : ""); + dst_printf(dst, ")\n"); + break; + } + case PGP_SIG_SUBPKT_SIGNERS_USER_ID: + dst_print_raw(dst, sname, subpkt.fields.signer.uid, subpkt.fields.signer.len); + break; + case PGP_SIG_SUBPKT_REVOCATION_REASON: { + int code = subpkt.fields.revocation_reason.code; + const char *reason = id_str_pair::lookup(revoc_reason_map, code, "Unknown"); + dst_printf(dst, "%s: %d (%s)\n", sname, code, reason); + dst_print_raw(dst, + "message", + subpkt.fields.revocation_reason.str, + subpkt.fields.revocation_reason.len); + break; + } + case PGP_SIG_SUBPKT_FEATURES: + dst_printf(dst, "%s: 0x%02x ( ", sname, subpkt.data[0]); + dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_MDC ? "mdc " : ""); + dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_AEAD ? "aead " : ""); + dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_V5 ? "v5 keys " : ""); + dst_printf(dst, ")\n"); + break; + case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE: + dst_printf(dst, "%s:\n", sname); + stream_dump_signature_pkt(ctx, subpkt.fields.sig, dst); + break; + case PGP_SIG_SUBPKT_ISSUER_FPR: + dst_print_hex( + dst, sname, subpkt.fields.issuer_fp.fp, subpkt.fields.issuer_fp.len, true); + break; + case PGP_SIG_SUBPKT_PREFERRED_AEAD: + dst_print_algs(dst, + "preferred aead algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + aead_alg_map); + break; + default: + if (!ctx->dump_packets) { + indent_dest_increase(dst); + dst_hexdump(dst, subpkt.data, subpkt.len); + indent_dest_decrease(dst); + } + } +} + +static void +signature_dump_subpackets(rnp_dump_ctx_t * ctx, + pgp_dest_t * dst, + pgp_signature_t *sig, + bool hashed) +{ + bool empty = true; + + for (auto &subpkt : sig->subpkts) { + if (subpkt.hashed != hashed) { + continue; + } + empty = false; + dst_printf(dst, ":type %d, len %d", (int) subpkt.type, (int) subpkt.len); + dst_printf(dst, "%s\n", subpkt.critical ? ", critical" : ""); + if (ctx->dump_packets) { + dst_printf(dst, ":subpacket contents:\n"); + indent_dest_increase(dst); + dst_hexdump(dst, subpkt.data, subpkt.len); + indent_dest_decrease(dst); + } + signature_dump_subpacket(ctx, dst, subpkt); + } + + if (empty) { + dst_printf(dst, "none\n"); + } +} + +static void +stream_dump_signature_pkt(rnp_dump_ctx_t *ctx, pgp_signature_t *sig, pgp_dest_t *dst) +{ + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) sig->version); + dst_print_sig_type(dst, "type", sig->type()); + if (sig->version < PGP_V4) { + dst_print_time(dst, "creation time", sig->creation_time); + dst_print_keyid(dst, "signing key id", sig->signer); + } + dst_print_palg(dst, NULL, sig->palg); + dst_print_halg(dst, NULL, sig->halg); + + if (sig->version >= PGP_V4) { + dst_printf(dst, "hashed subpackets:\n"); + indent_dest_increase(dst); + signature_dump_subpackets(ctx, dst, sig, true); + indent_dest_decrease(dst); + + dst_printf(dst, "unhashed subpackets:\n"); + indent_dest_increase(dst); + signature_dump_subpackets(ctx, dst, sig, false); + indent_dest_decrease(dst); + } + + dst_print_hex(dst, "lbits", sig->lbits, sizeof(sig->lbits), false); + dst_printf(dst, "signature material:\n"); + indent_dest_increase(dst); + + pgp_signature_material_t material = {}; + try { + sig->parse_material(material); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return; + } + switch (sig->palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + dst_print_mpi(dst, "rsa s", &material.rsa.s, ctx->dump_mpi); + break; + case PGP_PKA_DSA: + dst_print_mpi(dst, "dsa r", &material.dsa.r, ctx->dump_mpi); + dst_print_mpi(dst, "dsa s", &material.dsa.s, ctx->dump_mpi); + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + dst_print_mpi(dst, "ecc r", &material.ecc.r, ctx->dump_mpi); + dst_print_mpi(dst, "ecc s", &material.ecc.s, ctx->dump_mpi); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + dst_print_mpi(dst, "eg r", &material.eg.r, ctx->dump_mpi); + dst_print_mpi(dst, "eg s", &material.eg.s, ctx->dump_mpi); + break; + default: + dst_printf(dst, "unknown algorithm\n"); + } + indent_dest_decrease(dst); + indent_dest_decrease(dst); +} + +static rnp_result_t +stream_dump_signature(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_signature_t sig; + rnp_result_t ret; + + dst_printf(dst, "Signature packet\n"); + try { + ret = sig.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + indent_dest_increase(dst); + dst_printf(dst, "failed to parse\n"); + indent_dest_decrease(dst); + return ret; + } + stream_dump_signature_pkt(ctx, &sig, dst); + return ret; +} + +static rnp_result_t +stream_dump_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_key_pkt_t key; + rnp_result_t ret; + pgp_fingerprint_t keyfp = {}; + + try { + ret = key.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "%s packet\n", id_str_pair::lookup(key_type_map, key.tag, "Unknown")); + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) key.version); + dst_print_time(dst, "creation time", key.creation_time); + if (key.version < PGP_V4) { + dst_printf(dst, "v3 validity days: %d\n", (int) key.v3_days); + } + dst_print_palg(dst, NULL, key.alg); + dst_printf(dst, "public key material:\n"); + indent_dest_increase(dst); + + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + dst_print_mpi(dst, "rsa n", &key.material.rsa.n, ctx->dump_mpi); + dst_print_mpi(dst, "rsa e", &key.material.rsa.e, ctx->dump_mpi); + break; + case PGP_PKA_DSA: + dst_print_mpi(dst, "dsa p", &key.material.dsa.p, ctx->dump_mpi); + dst_print_mpi(dst, "dsa q", &key.material.dsa.q, ctx->dump_mpi); + dst_print_mpi(dst, "dsa g", &key.material.dsa.g, ctx->dump_mpi); + dst_print_mpi(dst, "dsa y", &key.material.dsa.y, ctx->dump_mpi); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + dst_print_mpi(dst, "eg p", &key.material.eg.p, ctx->dump_mpi); + dst_print_mpi(dst, "eg g", &key.material.eg.g, ctx->dump_mpi); + dst_print_mpi(dst, "eg y", &key.material.eg.y, ctx->dump_mpi); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + dst_print_mpi(dst, "ecc p", &key.material.ec.p, ctx->dump_mpi); + dst_printf(dst, "ecc curve: %s\n", cdesc ? cdesc->pgp_name : "unknown"); + break; + } + case PGP_PKA_ECDH: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + dst_print_mpi(dst, "ecdh p", &key.material.ec.p, ctx->dump_mpi); + dst_printf(dst, "ecdh curve: %s\n", cdesc ? cdesc->pgp_name : "unknown"); + dst_print_halg(dst, "ecdh hash algorithm", key.material.ec.kdf_hash_alg); + dst_printf(dst, "ecdh key wrap algorithm: %d\n", (int) key.material.ec.key_wrap_alg); + break; + } + default: + dst_printf(dst, "unknown public key algorithm\n"); + } + indent_dest_decrease(dst); + + if (is_secret_key_pkt(key.tag)) { + dst_printf(dst, "secret key material:\n"); + indent_dest_increase(dst); + + dst_printf(dst, "s2k usage: %d\n", (int) key.sec_protection.s2k.usage); + if ((key.sec_protection.s2k.usage == PGP_S2KU_ENCRYPTED) || + (key.sec_protection.s2k.usage == PGP_S2KU_ENCRYPTED_AND_HASHED)) { + dst_print_salg(dst, NULL, key.sec_protection.symm_alg); + dst_print_s2k(dst, &key.sec_protection.s2k); + if (key.sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) { + size_t bl_size = pgp_block_size(key.sec_protection.symm_alg); + if (bl_size) { + dst_print_hex(dst, "cipher iv", key.sec_protection.iv, bl_size, true); + } else { + dst_printf(dst, "cipher iv: unknown algorithm\n"); + } + } + dst_printf(dst, "encrypted secret key data: %d bytes\n", (int) key.sec_len); + } + + if (!key.sec_protection.s2k.usage) { + dst_printf(dst, "cleartext secret key data: %d bytes\n", (int) key.sec_len); + } + indent_dest_decrease(dst); + } + + pgp_key_id_t keyid = {}; + if (!pgp_keyid(keyid, key)) { + dst_print_hex(dst, "keyid", keyid.data(), keyid.size(), false); + } else { + dst_printf(dst, "keyid: failed to calculate"); + } + + if ((key.version > PGP_V3) && (ctx->dump_grips)) { + if (!pgp_fingerprint(keyfp, key)) { + dst_print_hex(dst, "fingerprint", keyfp.fingerprint, keyfp.length, false); + } else { + dst_printf(dst, "fingerprint: failed to calculate"); + } + } + + if (ctx->dump_grips) { + pgp_key_grip_t grip; + if (rnp_key_store_get_key_grip(&key.material, grip)) { + dst_print_hex(dst, "grip", grip.data(), grip.size(), false); + } else { + dst_printf(dst, "grip: failed to calculate"); + } + } + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_userid(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_userid_pkt_t uid; + rnp_result_t ret; + const char * utype; + + try { + ret = uid.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + switch (uid.tag) { + case PGP_PKT_USER_ID: + utype = "UserID"; + break; + case PGP_PKT_USER_ATTR: + utype = "UserAttr"; + break; + default: + utype = "Unknown user id"; + } + + dst_printf(dst, "%s packet\n", utype); + indent_dest_increase(dst); + + switch (uid.tag) { + case PGP_PKT_USER_ID: + dst_printf(dst, "id: "); + dst_write(dst, uid.uid, uid.uid_len); + dst_printf(dst, "\n"); + break; + case PGP_PKT_USER_ATTR: + dst_printf(dst, "id: (%d bytes of data)\n", (int) uid.uid_len); + break; + default:; + } + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_pk_session_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_pk_sesskey_t pkey; + pgp_encrypted_material_t material; + rnp_result_t ret; + + try { + ret = pkey.parse(*src); + if (!pkey.parse_material(material)) { + ret = RNP_ERROR_BAD_FORMAT; + } + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "Public-key encrypted session key packet\n"); + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) pkey.version); + dst_print_keyid(dst, NULL, pkey.key_id); + dst_print_palg(dst, NULL, pkey.alg); + dst_printf(dst, "encrypted material:\n"); + indent_dest_increase(dst); + + switch (pkey.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + dst_print_mpi(dst, "rsa m", &material.rsa.m, ctx->dump_mpi); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + dst_print_mpi(dst, "eg g", &material.eg.g, ctx->dump_mpi); + dst_print_mpi(dst, "eg m", &material.eg.m, ctx->dump_mpi); + break; + case PGP_PKA_SM2: + dst_print_mpi(dst, "sm2 m", &material.sm2.m, ctx->dump_mpi); + break; + case PGP_PKA_ECDH: + dst_print_mpi(dst, "ecdh p", &material.ecdh.p, ctx->dump_mpi); + if (ctx->dump_mpi) { + dst_print_hex(dst, "ecdh m", material.ecdh.m, material.ecdh.mlen, true); + } else { + dst_printf(dst, "ecdh m: %d bytes\n", (int) material.ecdh.mlen); + } + break; + default: + dst_printf(dst, "unknown public key algorithm\n"); + } + + indent_dest_decrease(dst); + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_sk_session_key(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_sk_sesskey_t skey; + rnp_result_t ret; + + try { + ret = skey.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "Symmetric-key encrypted session key packet\n"); + indent_dest_increase(dst); + dst_printf(dst, "version: %d\n", (int) skey.version); + dst_print_salg(dst, NULL, skey.alg); + if (skey.version == PGP_SKSK_V5) { + dst_print_aalg(dst, NULL, skey.aalg); + } + dst_print_s2k(dst, &skey.s2k); + if (skey.version == PGP_SKSK_V5) { + dst_print_hex(dst, "aead iv", skey.iv, skey.ivlen, true); + } + dst_print_hex(dst, "encrypted key", skey.enckey, skey.enckeylen, true); + indent_dest_decrease(dst); + + return RNP_SUCCESS; +} + +static bool +stream_dump_get_aead_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr) +{ + pgp_dest_t encdst = {}; + uint8_t encpkt[64] = {}; + + if (init_mem_dest(&encdst, &encpkt, sizeof(encpkt))) { + return false; + } + mem_dest_discard_overflow(&encdst, true); + + if (stream_read_packet(src, &encdst)) { + dst_close(&encdst, false); + return false; + } + size_t len = std::min(encdst.writeb, sizeof(encpkt)); + dst_close(&encdst, false); + + pgp_source_t memsrc = {}; + if (init_mem_src(&memsrc, encpkt, len, false)) { + return false; + } + bool res = get_aead_src_hdr(&memsrc, hdr); + src_close(&memsrc); + return res; +} + +static rnp_result_t +stream_dump_aead_encrypted(pgp_source_t *src, pgp_dest_t *dst) +{ + dst_printf(dst, "AEAD-encrypted data packet\n"); + + pgp_aead_hdr_t aead = {}; + if (!stream_dump_get_aead_hdr(src, &aead)) { + dst_printf(dst, "ERROR: failed to read AEAD header\n"); + return RNP_ERROR_READ; + } + + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) aead.version); + dst_print_salg(dst, NULL, aead.ealg); + dst_print_aalg(dst, NULL, aead.aalg); + dst_printf(dst, "chunk size: %d\n", (int) aead.csize); + dst_print_hex(dst, "initialization vector", aead.iv, aead.ivlen, true); + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_encrypted(pgp_source_t *src, pgp_dest_t *dst, int tag) +{ + switch (tag) { + case PGP_PKT_SE_DATA: + dst_printf(dst, "Symmetrically-encrypted data packet\n\n"); + break; + case PGP_PKT_SE_IP_DATA: + dst_printf(dst, "Symmetrically-encrypted integrity protected data packet\n\n"); + break; + case PGP_PKT_AEAD_ENCRYPTED: + return stream_dump_aead_encrypted(src, dst); + default: + dst_printf(dst, "Unknown encrypted data packet\n\n"); + break; + } + + return stream_skip_packet(src); +} + +static rnp_result_t +stream_dump_one_pass(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_one_pass_sig_t onepass; + rnp_result_t ret; + + try { + ret = onepass.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "One-pass signature packet\n"); + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) onepass.version); + dst_print_sig_type(dst, NULL, onepass.type); + dst_print_halg(dst, NULL, onepass.halg); + dst_print_palg(dst, NULL, onepass.palg); + dst_print_keyid(dst, "signing key id", onepass.keyid); + dst_printf(dst, "nested: %d\n", (int) onepass.nested); + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_compressed(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_source_t zsrc = {0}; + uint8_t zalg; + rnp_result_t ret; + + if ((ret = init_compressed_src(&zsrc, src))) { + return ret; + } + + dst_printf(dst, "Compressed data packet\n"); + indent_dest_increase(dst); + + get_compressed_src_alg(&zsrc, &zalg); + dst_print_zalg(dst, NULL, (pgp_compression_type_t) zalg); + dst_printf(dst, "Decompressed contents:\n"); + ret = stream_dump_packets_raw(ctx, &zsrc, dst); + + src_close(&zsrc); + indent_dest_decrease(dst); + return ret; +} + +static rnp_result_t +stream_dump_literal(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_source_t lsrc = {0}; + pgp_literal_hdr_t lhdr = {0}; + rnp_result_t ret; + uint8_t readbuf[16384]; + + if ((ret = init_literal_src(&lsrc, src))) { + return ret; + } + + dst_printf(dst, "Literal data packet\n"); + indent_dest_increase(dst); + + get_literal_src_hdr(&lsrc, &lhdr); + dst_printf(dst, "data format: '%c'\n", lhdr.format); + dst_printf(dst, "filename: %s (len %d)\n", lhdr.fname, (int) lhdr.fname_len); + dst_print_time(dst, "timestamp", lhdr.timestamp); + + ret = RNP_SUCCESS; + while (!src_eof(&lsrc)) { + size_t read = 0; + if (!src_read(&lsrc, readbuf, sizeof(readbuf), &read)) { + ret = RNP_ERROR_READ; + break; + } + } + + dst_printf(dst, "data bytes: %lu\n", (unsigned long) lsrc.readb); + src_close(&lsrc); + indent_dest_decrease(dst); + return ret; +} + +static rnp_result_t +stream_dump_marker(pgp_source_t &src, pgp_dest_t &dst) +{ + dst_printf(&dst, "Marker packet\n"); + indent_dest_increase(&dst); + rnp_result_t ret = stream_parse_marker(src); + dst_printf(&dst, "contents: %s\n", ret ? "invalid" : PGP_MARKER_CONTENTS); + indent_dest_decrease(&dst); + return ret; +} + +static rnp_result_t +stream_dump_packets_raw(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + char msg[1024 + PGP_MAX_HEADER_SIZE] = {0}; + char smsg[128] = {0}; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (src_eof(src)) { + return RNP_SUCCESS; + } + + /* do not allow endless recursion */ + if (++ctx->layers > MAXIMUM_NESTING_LEVEL) { + RNP_LOG("Too many OpenPGP nested layers during the dump."); + dst_printf(dst, ":too many OpenPGP packet layers, stopping.\n"); + ret = RNP_SUCCESS; + goto finish; + } + + while (!src_eof(src)) { + pgp_packet_hdr_t hdr = {}; + size_t off = src->readb; + rnp_result_t hdrret = stream_peek_packet_hdr(src, &hdr); + if (hdrret) { + ret = hdrret; + goto finish; + } + + if (hdr.partial) { + snprintf(msg, sizeof(msg), "partial len"); + } else if (hdr.indeterminate) { + snprintf(msg, sizeof(msg), "indeterminate len"); + } else { + snprintf(msg, sizeof(msg), "len %zu", hdr.pkt_len); + } + vsnprinthex(smsg, sizeof(smsg), hdr.hdr, hdr.hdr_len); + dst_printf( + dst, ":off %zu: packet header 0x%s (tag %d, %s)\n", off, smsg, hdr.tag, msg); + + if (ctx->dump_packets) { + size_t rlen = hdr.pkt_len + hdr.hdr_len; + bool part = false; + + if (!hdr.pkt_len || (rlen > 1024 + hdr.hdr_len)) { + rlen = 1024 + hdr.hdr_len; + part = true; + } + + dst_printf(dst, ":off %zu: packet contents ", off + hdr.hdr_len); + if (!src_peek(src, msg, rlen, &rlen)) { + dst_printf(dst, "- failed to read\n"); + } else { + rlen -= hdr.hdr_len; + if (part || (rlen < hdr.pkt_len)) { + dst_printf(dst, "(first %d bytes)\n", (int) rlen); + } else { + dst_printf(dst, "(%d bytes)\n", (int) rlen); + } + indent_dest_increase(dst); + dst_hexdump(dst, (uint8_t *) msg + hdr.hdr_len, rlen); + indent_dest_decrease(dst); + } + dst_printf(dst, "\n"); + } + + switch (hdr.tag) { + case PGP_PKT_SIGNATURE: + ret = stream_dump_signature(ctx, src, dst); + break; + case PGP_PKT_SECRET_KEY: + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_SECRET_SUBKEY: + case PGP_PKT_PUBLIC_SUBKEY: + ret = stream_dump_key(ctx, src, dst); + break; + case PGP_PKT_USER_ID: + case PGP_PKT_USER_ATTR: + ret = stream_dump_userid(src, dst); + break; + case PGP_PKT_PK_SESSION_KEY: + ret = stream_dump_pk_session_key(ctx, src, dst); + break; + case PGP_PKT_SK_SESSION_KEY: + ret = stream_dump_sk_session_key(src, dst); + break; + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_AEAD_ENCRYPTED: + ctx->stream_pkts++; + ret = stream_dump_encrypted(src, dst, hdr.tag); + break; + case PGP_PKT_ONE_PASS_SIG: + ret = stream_dump_one_pass(src, dst); + break; + case PGP_PKT_COMPRESSED: + ctx->stream_pkts++; + ret = stream_dump_compressed(ctx, src, dst); + break; + case PGP_PKT_LITDATA: + ctx->stream_pkts++; + ret = stream_dump_literal(src, dst); + break; + case PGP_PKT_MARKER: + ret = stream_dump_marker(*src, *dst); + break; + case PGP_PKT_TRUST: + case PGP_PKT_MDC: + dst_printf(dst, "Skipping unhandled pkt: %d\n\n", (int) hdr.tag); + ret = stream_skip_packet(src); + break; + default: + dst_printf(dst, "Skipping Unknown pkt: %d\n\n", (int) hdr.tag); + ret = stream_skip_packet(src); + if (ret) { + goto finish; + } + } + + if (ret) { + RNP_LOG("failed to process packet"); + if (++ctx->failures > MAXIMUM_ERROR_PKTS) { + RNP_LOG("too many packet dump errors."); + goto finish; + } + } + + if (ctx->stream_pkts > MAXIMUM_STREAM_PKTS) { + RNP_LOG("Too many OpenPGP stream packets during the dump."); + dst_printf(dst, ":too many OpenPGP stream packets, stopping.\n"); + ret = RNP_SUCCESS; + goto finish; + } + } + + ret = RNP_SUCCESS; +finish: + return ret; +} + +static bool +stream_skip_cleartext(pgp_source_t *src) +{ + char buf[4096]; + size_t read = 0; + size_t siglen = strlen(ST_SIG_BEGIN); + char * hdrpos; + + while (!src_eof(src)) { + if (!src_peek(src, buf, sizeof(buf) - 1, &read) || (read <= siglen)) { + return false; + } + buf[read] = '\0'; + + if ((hdrpos = strstr(buf, ST_SIG_BEGIN))) { + /* +1 here is to skip \n on the beginning of ST_SIG_BEGIN */ + src_skip(src, hdrpos - buf + 1); + return true; + } + src_skip(src, read - siglen + 1); + } + return false; +} + +rnp_result_t +stream_dump_packets(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_source_t armorsrc = {0}; + pgp_dest_t wrdst = {0}; + bool armored = false; + bool indent = false; + rnp_result_t ret = RNP_ERROR_GENERIC; + + ctx->layers = 0; + ctx->stream_pkts = 0; + ctx->failures = 0; + /* check whether source is cleartext - then skip till the signature */ + if (is_cleartext_source(src)) { + dst_printf(dst, ":cleartext signed data\n"); + if (!stream_skip_cleartext(src)) { + RNP_LOG("malformed cleartext signed data"); + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + } + /* check whether source is armored */ + if (is_armored_source(src)) { + if ((ret = init_armored_src(&armorsrc, src))) { + RNP_LOG("failed to parse armored data"); + goto finish; + } + armored = true; + src = &armorsrc; + dst_printf(dst, ":armored input\n"); + } + + if (src_eof(src)) { + dst_printf(dst, ":empty input\n"); + ret = RNP_SUCCESS; + goto finish; + } + + if ((ret = init_indent_dest(&wrdst, dst))) { + RNP_LOG("failed to init indent dest"); + goto finish; + } + indent = true; + indent_dest_set(&wrdst, 0); + + ret = stream_dump_packets_raw(ctx, src, &wrdst); +finish: + if (armored) { + src_close(&armorsrc); + } + if (indent) { + dst_close(&wrdst, false); + } + return ret; +} + +static bool +obj_add_intstr_json(json_object *obj, const char *name, int val, const id_str_pair map[]) +{ + if (!obj_add_field_json(obj, name, json_object_new_int(val))) { + return false; + } + if (!map) { + return true; + } + char namestr[64] = {0}; + const char *str = id_str_pair::lookup(map, val, "Unknown"); + snprintf(namestr, sizeof(namestr), "%s.str", name); + return obj_add_field_json(obj, namestr, json_object_new_string(str)); +} + +static bool +obj_add_mpi_json(json_object *obj, const char *name, const pgp_mpi_t *mpi, bool contents) +{ + char strname[64] = {0}; + snprintf(strname, sizeof(strname), "%s.bits", name); + if (!obj_add_field_json(obj, strname, json_object_new_int(mpi_bits(mpi)))) { + return false; + } + if (!contents) { + return true; + } + snprintf(strname, sizeof(strname), "%s.raw", name); + return obj_add_hex_json(obj, strname, mpi->mpi, mpi->len); +} + +static bool +subpacket_obj_add_algs( + json_object *obj, const char *name, uint8_t *algs, size_t len, const id_str_pair map[]) +{ + json_object *jso_algs = json_object_new_array(); + if (!jso_algs || !obj_add_field_json(obj, name, jso_algs)) { + return false; + } + for (size_t i = 0; i < len; i++) { + if (!array_add_element_json(jso_algs, json_object_new_int(algs[i]))) { + return false; + } + } + if (!map) { + return true; + } + + char strname[64] = {0}; + snprintf(strname, sizeof(strname), "%s.str", name); + + jso_algs = json_object_new_array(); + if (!jso_algs || !obj_add_field_json(obj, strname, jso_algs)) { + return false; + } + for (size_t i = 0; i < len; i++) { + if (!array_add_element_json( + jso_algs, + json_object_new_string(id_str_pair::lookup(map, algs[i], "Unknown")))) { + return false; + } + } + return true; +} + +static bool +obj_add_s2k_json(json_object *obj, pgp_s2k_t *s2k) +{ + json_object *s2k_obj = json_object_new_object(); + if (!obj_add_field_json(obj, "s2k", s2k_obj)) { + return false; + } + if (!obj_add_field_json(s2k_obj, "specifier", json_object_new_int(s2k->specifier))) { + return false; + } + if ((s2k->specifier == PGP_S2KS_EXPERIMENTAL) && s2k->gpg_ext_num) { + if (!obj_add_field_json( + s2k_obj, "gpg extension", json_object_new_int(s2k->gpg_ext_num))) { + return false; + } + if (s2k->gpg_ext_num == PGP_S2K_GPG_SMARTCARD) { + size_t slen = s2k->gpg_serial_len > 16 ? 16 : s2k->gpg_serial_len; + if (!obj_add_hex_json(s2k_obj, "card serial number", s2k->gpg_serial, slen)) { + return false; + } + } + } + if (s2k->specifier == PGP_S2KS_EXPERIMENTAL) { + return obj_add_hex_json( + s2k_obj, "unknown experimental", s2k->experimental.data(), s2k->experimental.size()); + } + if (!obj_add_intstr_json(s2k_obj, "hash algorithm", s2k->hash_alg, hash_alg_map)) { + return false; + } + if (((s2k->specifier == PGP_S2KS_SALTED) || + (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED)) && + !obj_add_hex_json(s2k_obj, "salt", s2k->salt, PGP_SALT_SIZE)) { + return false; + } + if (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED) { + size_t real_iter = pgp_s2k_decode_iterations(s2k->iterations); + if (!obj_add_field_json(s2k_obj, "iterations", json_object_new_int(real_iter))) { + return false; + } + } + return true; +} + +static rnp_result_t stream_dump_signature_pkt_json(rnp_dump_ctx_t * ctx, + const pgp_signature_t *sig, + json_object * pkt); + +static bool +signature_dump_subpacket_json(rnp_dump_ctx_t * ctx, + const pgp_sig_subpkt_t &subpkt, + json_object * obj) +{ + switch (subpkt.type) { + case PGP_SIG_SUBPKT_CREATION_TIME: + return obj_add_field_json( + obj, "creation time", json_object_new_int64(subpkt.fields.create)); + case PGP_SIG_SUBPKT_EXPIRATION_TIME: + return obj_add_field_json( + obj, "expiration time", json_object_new_int64(subpkt.fields.expiry)); + case PGP_SIG_SUBPKT_EXPORT_CERT: + return obj_add_field_json( + obj, "exportable", json_object_new_boolean(subpkt.fields.exportable)); + case PGP_SIG_SUBPKT_TRUST: + return obj_add_field_json( + obj, "amount", json_object_new_int(subpkt.fields.trust.amount)) && + obj_add_field_json( + obj, "level", json_object_new_int(subpkt.fields.trust.level)); + case PGP_SIG_SUBPKT_REGEXP: + return obj_add_field_json( + obj, + "regexp", + json_object_new_string_len(subpkt.fields.regexp.str, subpkt.fields.regexp.len)); + case PGP_SIG_SUBPKT_REVOCABLE: + return obj_add_field_json( + obj, "revocable", json_object_new_boolean(subpkt.fields.revocable)); + case PGP_SIG_SUBPKT_KEY_EXPIRY: + return obj_add_field_json( + obj, "key expiration", json_object_new_int64(subpkt.fields.expiry)); + case PGP_SIG_SUBPKT_PREFERRED_SKA: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + symm_alg_map); + case PGP_SIG_SUBPKT_PREFERRED_HASH: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + hash_alg_map); + case PGP_SIG_SUBPKT_PREF_COMPRESS: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + z_alg_map); + case PGP_SIG_SUBPKT_PREFERRED_AEAD: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + aead_alg_map); + case PGP_SIG_SUBPKT_REVOCATION_KEY: + return obj_add_field_json( + obj, "class", json_object_new_int(subpkt.fields.revocation_key.klass)) && + obj_add_field_json( + obj, "algorithm", json_object_new_int(subpkt.fields.revocation_key.pkalg)) && + obj_add_hex_json( + obj, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_SIZE); + case PGP_SIG_SUBPKT_ISSUER_KEY_ID: + return obj_add_hex_json(obj, "issuer keyid", subpkt.fields.issuer, PGP_KEY_ID_SIZE); + case PGP_SIG_SUBPKT_KEYSERV_PREFS: + return obj_add_field_json( + obj, "no-modify", json_object_new_boolean(subpkt.fields.ks_prefs.no_modify)); + case PGP_SIG_SUBPKT_PREF_KEYSERV: + return obj_add_field_json(obj, + "uri", + json_object_new_string_len(subpkt.fields.preferred_ks.uri, + subpkt.fields.preferred_ks.len)); + case PGP_SIG_SUBPKT_PRIMARY_USER_ID: + return obj_add_field_json( + obj, "primary", json_object_new_boolean(subpkt.fields.primary_uid)); + case PGP_SIG_SUBPKT_POLICY_URI: + return obj_add_field_json( + obj, + "uri", + json_object_new_string_len(subpkt.fields.policy.uri, subpkt.fields.policy.len)); + case PGP_SIG_SUBPKT_KEY_FLAGS: { + uint8_t flg = subpkt.fields.key_flags; + if (!obj_add_field_json(obj, "flags", json_object_new_int(flg))) { + return false; + } + json_object *jso_flg = json_object_new_array(); + if (!jso_flg || !obj_add_field_json(obj, "flags.str", jso_flg)) { + return false; + } + if ((flg & PGP_KF_CERTIFY) && + !array_add_element_json(jso_flg, json_object_new_string("certify"))) { + return false; + } + if ((flg & PGP_KF_SIGN) && + !array_add_element_json(jso_flg, json_object_new_string("sign"))) { + return false; + } + if ((flg & PGP_KF_ENCRYPT_COMMS) && + !array_add_element_json(jso_flg, json_object_new_string("encrypt_comm"))) { + return false; + } + if ((flg & PGP_KF_ENCRYPT_STORAGE) && + !array_add_element_json(jso_flg, json_object_new_string("encrypt_storage"))) { + return false; + } + if ((flg & PGP_KF_SPLIT) && + !array_add_element_json(jso_flg, json_object_new_string("split"))) { + return false; + } + if ((flg & PGP_KF_AUTH) && + !array_add_element_json(jso_flg, json_object_new_string("auth"))) { + return false; + } + if ((flg & PGP_KF_SHARED) && + !array_add_element_json(jso_flg, json_object_new_string("shared"))) { + return false; + } + return true; + } + case PGP_SIG_SUBPKT_SIGNERS_USER_ID: + return obj_add_field_json( + obj, + "uid", + json_object_new_string_len(subpkt.fields.signer.uid, subpkt.fields.signer.len)); + case PGP_SIG_SUBPKT_REVOCATION_REASON: { + if (!obj_add_intstr_json( + obj, "code", subpkt.fields.revocation_reason.code, revoc_reason_map)) { + return false; + } + return obj_add_field_json( + obj, + "message", + json_object_new_string_len(subpkt.fields.revocation_reason.str, + subpkt.fields.revocation_reason.len)); + } + case PGP_SIG_SUBPKT_FEATURES: + return obj_add_field_json( + obj, + "mdc", + json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_MDC)) && + obj_add_field_json( + obj, + "aead", + json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_AEAD)) && + obj_add_field_json( + obj, + "v5 keys", + json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_V5)); + case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE: { + json_object *sig = json_object_new_object(); + if (!sig || !obj_add_field_json(obj, "signature", sig)) { + return false; + } + return !stream_dump_signature_pkt_json(ctx, subpkt.fields.sig, sig); + } + case PGP_SIG_SUBPKT_ISSUER_FPR: + return obj_add_hex_json( + obj, "fingerprint", subpkt.fields.issuer_fp.fp, subpkt.fields.issuer_fp.len); + case PGP_SIG_SUBPKT_NOTATION_DATA: { + bool human = subpkt.fields.notation.human; + if (!json_add(obj, "human", human) || !json_add(obj, + "name", + (char *) subpkt.fields.notation.name, + subpkt.fields.notation.nlen)) { + return false; + } + if (human) { + return json_add(obj, + "value", + (char *) subpkt.fields.notation.value, + subpkt.fields.notation.vlen); + } + return obj_add_hex_json( + obj, "value", subpkt.fields.notation.value, subpkt.fields.notation.vlen); + } + default: + if (!ctx->dump_packets) { + return obj_add_hex_json(obj, "raw", subpkt.data, subpkt.len); + } + return true; + } + return true; +} + +static json_object * +signature_dump_subpackets_json(rnp_dump_ctx_t *ctx, const pgp_signature_t *sig) +{ + json_object *res = json_object_new_array(); + + for (auto &subpkt : sig->subpkts) { + json_object *jso_subpkt = json_object_new_object(); + if (json_object_array_add(res, jso_subpkt)) { + json_object_put(jso_subpkt); + goto error; + } + + if (!obj_add_intstr_json(jso_subpkt, "type", subpkt.type, sig_subpkt_type_map)) { + goto error; + } + if (!obj_add_field_json(jso_subpkt, "length", json_object_new_int(subpkt.len))) { + goto error; + } + if (!obj_add_field_json( + jso_subpkt, "hashed", json_object_new_boolean(subpkt.hashed))) { + goto error; + } + if (!obj_add_field_json( + jso_subpkt, "critical", json_object_new_boolean(subpkt.critical))) { + goto error; + } + + if (ctx->dump_packets && + !obj_add_hex_json(jso_subpkt, "raw", subpkt.data, subpkt.len)) { + goto error; + } + + if (!signature_dump_subpacket_json(ctx, subpkt, jso_subpkt)) { + goto error; + } + } + + return res; +error: + json_object_put(res); + return NULL; +} + +static rnp_result_t +stream_dump_signature_pkt_json(rnp_dump_ctx_t * ctx, + const pgp_signature_t *sig, + json_object * pkt) +{ + json_object * material = NULL; + pgp_signature_material_t sigmaterial = {}; + rnp_result_t ret = RNP_ERROR_OUT_OF_MEMORY; + + if (!obj_add_field_json(pkt, "version", json_object_new_int(sig->version))) { + goto done; + } + if (!obj_add_intstr_json(pkt, "type", sig->type(), sig_type_map)) { + goto done; + } + + if (sig->version < PGP_V4) { + if (!obj_add_field_json( + pkt, "creation time", json_object_new_int(sig->creation_time))) { + goto done; + } + if (!obj_add_hex_json(pkt, "signer", sig->signer.data(), sig->signer.size())) { + goto done; + } + } + if (!obj_add_intstr_json(pkt, "algorithm", sig->palg, pubkey_alg_map)) { + goto done; + } + if (!obj_add_intstr_json(pkt, "hash algorithm", sig->halg, hash_alg_map)) { + goto done; + } + + if (sig->version >= PGP_V4) { + json_object *subpkts = signature_dump_subpackets_json(ctx, sig); + if (!subpkts) { + goto done; + } + if (!obj_add_field_json(pkt, "subpackets", subpkts)) { + goto done; + } + } + + if (!obj_add_hex_json(pkt, "lbits", sig->lbits, sizeof(sig->lbits))) { + goto done; + } + + material = json_object_new_object(); + if (!material || !obj_add_field_json(pkt, "material", material)) { + goto done; + } + + try { + sig->parse_material(sigmaterial); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + switch (sig->palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!obj_add_mpi_json(material, "s", &sigmaterial.rsa.s, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_DSA: + if (!obj_add_mpi_json(material, "r", &sigmaterial.dsa.r, ctx->dump_mpi) || + !obj_add_mpi_json(material, "s", &sigmaterial.dsa.s, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + if (!obj_add_mpi_json(material, "r", &sigmaterial.ecc.r, ctx->dump_mpi) || + !obj_add_mpi_json(material, "s", &sigmaterial.ecc.s, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!obj_add_mpi_json(material, "r", &sigmaterial.eg.r, ctx->dump_mpi) || + !obj_add_mpi_json(material, "s", &sigmaterial.eg.s, ctx->dump_mpi)) { + goto done; + } + break; + default: + break; + } + ret = RNP_SUCCESS; +done: + return ret; +} + +static rnp_result_t +stream_dump_signature_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_signature_t sig; + rnp_result_t ret; + try { + ret = sig.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + return stream_dump_signature_pkt_json(ctx, &sig, pkt); +} + +static rnp_result_t +stream_dump_key_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_key_pkt_t key; + rnp_result_t ret; + pgp_key_id_t keyid = {}; + pgp_fingerprint_t keyfp = {}; + json_object * material = NULL; + + try { + ret = key.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + ret = RNP_ERROR_OUT_OF_MEMORY; + + if (!obj_add_field_json(pkt, "version", json_object_new_int(key.version))) { + goto done; + } + if (!obj_add_field_json(pkt, "creation time", json_object_new_int64(key.creation_time))) { + goto done; + } + if ((key.version < PGP_V4) && + !obj_add_field_json(pkt, "v3 days", json_object_new_int(key.v3_days))) { + goto done; + } + if (!obj_add_intstr_json(pkt, "algorithm", key.alg, pubkey_alg_map)) { + goto done; + } + + material = json_object_new_object(); + if (!material || !obj_add_field_json(pkt, "material", material)) { + goto done; + } + + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!obj_add_mpi_json(material, "n", &key.material.rsa.n, ctx->dump_mpi) || + !obj_add_mpi_json(material, "e", &key.material.rsa.e, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_DSA: + if (!obj_add_mpi_json(material, "p", &key.material.dsa.p, ctx->dump_mpi) || + !obj_add_mpi_json(material, "q", &key.material.dsa.q, ctx->dump_mpi) || + !obj_add_mpi_json(material, "g", &key.material.dsa.g, ctx->dump_mpi) || + !obj_add_mpi_json(material, "y", &key.material.dsa.y, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!obj_add_mpi_json(material, "p", &key.material.eg.p, ctx->dump_mpi) || + !obj_add_mpi_json(material, "g", &key.material.eg.g, ctx->dump_mpi) || + !obj_add_mpi_json(material, "y", &key.material.eg.y, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + if (!obj_add_mpi_json(material, "p", &key.material.ec.p, ctx->dump_mpi)) { + goto done; + } + if (!obj_add_field_json(material, + "curve", + json_object_new_string(cdesc ? cdesc->pgp_name : "unknown"))) { + goto done; + } + break; + } + case PGP_PKA_ECDH: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + if (!obj_add_mpi_json(material, "p", &key.material.ec.p, ctx->dump_mpi)) { + goto done; + } + if (!obj_add_field_json(material, + "curve", + json_object_new_string(cdesc ? cdesc->pgp_name : "unknown"))) { + goto done; + } + if (!obj_add_intstr_json( + material, "hash algorithm", key.material.ec.kdf_hash_alg, hash_alg_map)) { + goto done; + } + if (!obj_add_intstr_json( + material, "key wrap algorithm", key.material.ec.key_wrap_alg, symm_alg_map)) { + goto done; + } + break; + } + default: + break; + } + + if (is_secret_key_pkt(key.tag)) { + if (!obj_add_field_json( + material, "s2k usage", json_object_new_int(key.sec_protection.s2k.usage))) { + goto done; + } + if (!obj_add_s2k_json(material, &key.sec_protection.s2k)) { + goto done; + } + if (key.sec_protection.s2k.usage && + !obj_add_intstr_json( + material, "symmetric algorithm", key.sec_protection.symm_alg, symm_alg_map)) { + goto done; + } + } + + if (pgp_keyid(keyid, key) || !obj_add_hex_json(pkt, "keyid", keyid.data(), keyid.size())) { + goto done; + } + + if (ctx->dump_grips) { + if (pgp_fingerprint(keyfp, key) || + !obj_add_hex_json(pkt, "fingerprint", keyfp.fingerprint, keyfp.length)) { + goto done; + } + + pgp_key_grip_t grip; + if (!rnp_key_store_get_key_grip(&key.material, grip) || + !obj_add_hex_json(pkt, "grip", grip.data(), grip.size())) { + goto done; + } + } + ret = RNP_SUCCESS; +done: + return ret; +} + +static rnp_result_t +stream_dump_userid_json(pgp_source_t *src, json_object *pkt) +{ + pgp_userid_pkt_t uid; + rnp_result_t ret; + + try { + ret = uid.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + switch (uid.tag) { + case PGP_PKT_USER_ID: + if (!obj_add_field_json( + pkt, "userid", json_object_new_string_len((char *) uid.uid, uid.uid_len))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKT_USER_ATTR: + if (!obj_add_hex_json(pkt, "userattr", uid.uid, uid.uid_len)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + default:; + } + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_pk_session_key_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_pk_sesskey_t pkey; + pgp_encrypted_material_t pkmaterial; + rnp_result_t ret; + + try { + ret = pkey.parse(*src); + if (!pkey.parse_material(pkmaterial)) { + ret = RNP_ERROR_BAD_FORMAT; + } + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(pkey.version)) || + !obj_add_hex_json(pkt, "keyid", pkey.key_id.data(), pkey.key_id.size()) || + !obj_add_intstr_json(pkt, "algorithm", pkey.alg, pubkey_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + json_object *material = json_object_new_object(); + if (!obj_add_field_json(pkt, "material", material)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + switch (pkey.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!obj_add_mpi_json(material, "m", &pkmaterial.rsa.m, ctx->dump_mpi)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!obj_add_mpi_json(material, "g", &pkmaterial.eg.g, ctx->dump_mpi) || + !obj_add_mpi_json(material, "m", &pkmaterial.eg.m, ctx->dump_mpi)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKA_SM2: + if (!obj_add_mpi_json(material, "m", &pkmaterial.sm2.m, ctx->dump_mpi)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKA_ECDH: + if (!obj_add_mpi_json(material, "p", &pkmaterial.ecdh.p, ctx->dump_mpi) || + !obj_add_field_json( + material, "m.bytes", json_object_new_int(pkmaterial.ecdh.mlen))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (ctx->dump_mpi && + !obj_add_hex_json(material, "m", pkmaterial.ecdh.m, pkmaterial.ecdh.mlen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + default:; + } + + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_sk_session_key_json(pgp_source_t *src, json_object *pkt) +{ + pgp_sk_sesskey_t skey; + rnp_result_t ret; + + try { + ret = skey.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(skey.version)) || + !obj_add_intstr_json(pkt, "algorithm", skey.alg, symm_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if ((skey.version == PGP_SKSK_V5) && + !obj_add_intstr_json(pkt, "aead algorithm", skey.aalg, aead_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_s2k_json(pkt, &skey.s2k)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if ((skey.version == PGP_SKSK_V5) && + !obj_add_hex_json(pkt, "aead iv", skey.iv, skey.ivlen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_hex_json(pkt, "encrypted key", skey.enckey, skey.enckeylen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_encrypted_json(pgp_source_t *src, json_object *pkt, pgp_pkt_type_t tag) +{ + if (tag != PGP_PKT_AEAD_ENCRYPTED) { + /* packet header with tag is already in pkt */ + return stream_skip_packet(src); + } + + /* dumping AEAD data */ + pgp_aead_hdr_t aead = {}; + if (!stream_dump_get_aead_hdr(src, &aead)) { + return RNP_ERROR_READ; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(aead.version)) || + !obj_add_intstr_json(pkt, "algorithm", aead.ealg, symm_alg_map) || + !obj_add_intstr_json(pkt, "aead algorithm", aead.aalg, aead_alg_map) || + !obj_add_field_json(pkt, "chunk size", json_object_new_int(aead.csize)) || + !obj_add_hex_json(pkt, "aead iv", aead.iv, aead.ivlen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_one_pass_json(pgp_source_t *src, json_object *pkt) +{ + pgp_one_pass_sig_t onepass; + rnp_result_t ret; + + try { + ret = onepass.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(onepass.version))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_intstr_json(pkt, "type", onepass.type, sig_type_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_intstr_json(pkt, "hash algorithm", onepass.halg, hash_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_intstr_json(pkt, "public key algorithm", onepass.palg, pubkey_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_hex_json(pkt, "signer", onepass.keyid.data(), onepass.keyid.size())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_field_json(pkt, "nested", json_object_new_boolean(onepass.nested))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_marker_json(pgp_source_t &src, json_object *pkt) +{ + rnp_result_t ret = stream_parse_marker(src); + + if (!obj_add_field_json( + pkt, "contents", json_object_new_string(ret ? "invalid" : PGP_MARKER_CONTENTS))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return ret; +} + +static rnp_result_t stream_dump_raw_packets_json(rnp_dump_ctx_t *ctx, + pgp_source_t * src, + json_object ** jso); + +static rnp_result_t +stream_dump_compressed_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_source_t zsrc = {0}; + uint8_t zalg; + rnp_result_t ret; + json_object *contents = NULL; + + if ((ret = init_compressed_src(&zsrc, src))) { + return ret; + } + + get_compressed_src_alg(&zsrc, &zalg); + if (!obj_add_intstr_json(pkt, "algorithm", zalg, z_alg_map)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + ret = stream_dump_raw_packets_json(ctx, &zsrc, &contents); + if (!ret && !obj_add_field_json(pkt, "contents", contents)) { + json_object_put(contents); + ret = RNP_ERROR_OUT_OF_MEMORY; + } +done: + src_close(&zsrc); + return ret; +} + +static rnp_result_t +stream_dump_literal_json(pgp_source_t *src, json_object *pkt) +{ + pgp_source_t lsrc = {0}; + pgp_literal_hdr_t lhdr = {0}; + rnp_result_t ret; + uint8_t readbuf[16384]; + + if ((ret = init_literal_src(&lsrc, src))) { + return ret; + } + ret = RNP_ERROR_OUT_OF_MEMORY; + get_literal_src_hdr(&lsrc, &lhdr); + if (!obj_add_field_json( + pkt, "format", json_object_new_string_len((char *) &lhdr.format, 1))) { + goto done; + } + if (!obj_add_field_json( + pkt, "filename", json_object_new_string_len(lhdr.fname, lhdr.fname_len))) { + goto done; + } + if (!obj_add_field_json(pkt, "timestamp", json_object_new_int64(lhdr.timestamp))) { + goto done; + } + + while (!src_eof(&lsrc)) { + size_t read = 0; + if (!src_read(&lsrc, readbuf, sizeof(readbuf), &read)) { + ret = RNP_ERROR_READ; + goto done; + } + } + + if (!obj_add_field_json(pkt, "datalen", json_object_new_int64(lsrc.readb))) { + goto done; + } + ret = RNP_SUCCESS; +done: + src_close(&lsrc); + return ret; +} + +static bool +stream_dump_hdr_json(pgp_source_t *src, pgp_packet_hdr_t *hdr, json_object *pkt) +{ + rnp_result_t hdrret = stream_peek_packet_hdr(src, hdr); + if (hdrret) { + return false; + } + + json_object *jso_hdr = json_object_new_object(); + if (!jso_hdr) { + return false; + } + + if (!obj_add_field_json(jso_hdr, "offset", json_object_new_int64(src->readb))) { + goto error; + } + if (!obj_add_intstr_json(jso_hdr, "tag", hdr->tag, packet_tag_map)) { + goto error; + } + if (!obj_add_hex_json(jso_hdr, "raw", hdr->hdr, hdr->hdr_len)) { + goto error; + } + if (!hdr->partial && !hdr->indeterminate && + !obj_add_field_json(jso_hdr, "length", json_object_new_int64(hdr->pkt_len))) { + goto error; + } + if (!obj_add_field_json(jso_hdr, "partial", json_object_new_boolean(hdr->partial))) { + goto error; + } + if (!obj_add_field_json( + jso_hdr, "indeterminate", json_object_new_boolean(hdr->indeterminate))) { + goto error; + } + return obj_add_field_json(pkt, "header", jso_hdr); +error: + json_object_put(jso_hdr); + return false; +} + +static rnp_result_t +stream_dump_raw_packets_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object **jso) +{ + json_object *pkts = NULL; + json_object *pkt = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + + pkts = json_object_new_array(); + if (!pkts) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (src_eof(src)) { + ret = RNP_SUCCESS; + goto done; + } + + /* do not allow endless recursion */ + if (++ctx->layers > MAXIMUM_NESTING_LEVEL) { + RNP_LOG("Too many OpenPGP nested layers during the dump."); + ret = RNP_SUCCESS; + goto done; + } + + while (!src_eof(src)) { + pgp_packet_hdr_t hdr = {}; + + pkt = json_object_new_object(); + if (!pkt) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (!stream_dump_hdr_json(src, &hdr, pkt)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (ctx->dump_packets) { + size_t rlen = hdr.pkt_len + hdr.hdr_len; + uint8_t buf[2048 + sizeof(hdr.hdr)] = {0}; + + if (!hdr.pkt_len || (rlen > 2048 + hdr.hdr_len)) { + rlen = 2048 + hdr.hdr_len; + } + if (!src_peek(src, buf, rlen, &rlen) || (rlen < hdr.hdr_len)) { + ret = RNP_ERROR_READ; + goto done; + } + if (!obj_add_hex_json(pkt, "raw", buf + hdr.hdr_len, rlen - hdr.hdr_len)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + + switch (hdr.tag) { + case PGP_PKT_SIGNATURE: + ret = stream_dump_signature_json(ctx, src, pkt); + break; + case PGP_PKT_SECRET_KEY: + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_SECRET_SUBKEY: + case PGP_PKT_PUBLIC_SUBKEY: + ret = stream_dump_key_json(ctx, src, pkt); + break; + case PGP_PKT_USER_ID: + case PGP_PKT_USER_ATTR: + ret = stream_dump_userid_json(src, pkt); + break; + case PGP_PKT_PK_SESSION_KEY: + ret = stream_dump_pk_session_key_json(ctx, src, pkt); + break; + case PGP_PKT_SK_SESSION_KEY: + ret = stream_dump_sk_session_key_json(src, pkt); + break; + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_AEAD_ENCRYPTED: + ctx->stream_pkts++; + ret = stream_dump_encrypted_json(src, pkt, hdr.tag); + break; + case PGP_PKT_ONE_PASS_SIG: + ret = stream_dump_one_pass_json(src, pkt); + break; + case PGP_PKT_COMPRESSED: + ctx->stream_pkts++; + ret = stream_dump_compressed_json(ctx, src, pkt); + break; + case PGP_PKT_LITDATA: + ctx->stream_pkts++; + ret = stream_dump_literal_json(src, pkt); + break; + case PGP_PKT_MARKER: + ret = stream_dump_marker_json(*src, pkt); + break; + case PGP_PKT_TRUST: + case PGP_PKT_MDC: + ret = stream_skip_packet(src); + break; + default: + ret = stream_skip_packet(src); + } + + if (ret) { + RNP_LOG("failed to process packet"); + if (++ctx->failures > MAXIMUM_ERROR_PKTS) { + RNP_LOG("too many packet dump errors."); + goto done; + } + ret = RNP_SUCCESS; + } + + if (json_object_array_add(pkts, pkt)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + if (ctx->stream_pkts > MAXIMUM_STREAM_PKTS) { + RNP_LOG("Too many OpenPGP stream packets during the dump."); + ret = RNP_SUCCESS; + goto done; + } + + pkt = NULL; + } +done: + if (ret) { + json_object_put(pkts); + json_object_put(pkt); + pkts = NULL; + } + *jso = pkts; + return ret; +} + +rnp_result_t +stream_dump_packets_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object **jso) +{ + pgp_source_t armorsrc = {0}; + bool armored = false; + rnp_result_t ret = RNP_ERROR_GENERIC; + + ctx->layers = 0; + ctx->stream_pkts = 0; + ctx->failures = 0; + /* check whether source is cleartext - then skip till the signature */ + if (is_cleartext_source(src)) { + if (!stream_skip_cleartext(src)) { + RNP_LOG("malformed cleartext signed data"); + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + } + /* check whether source is armored */ + if (is_armored_source(src)) { + if ((ret = init_armored_src(&armorsrc, src))) { + RNP_LOG("failed to parse armored data"); + goto finish; + } + armored = true; + src = &armorsrc; + } + + if (src_eof(src)) { + ret = RNP_ERROR_NOT_ENOUGH_DATA; + goto finish; + } + + ret = stream_dump_raw_packets_json(ctx, src, jso); +finish: + if (armored) { + src_close(&armorsrc); + } + return ret; +} diff --git a/src/librepgp/stream-dump.h b/src/librepgp/stream-dump.h new file mode 100644 index 0000000..6c2fcf1 --- /dev/null +++ b/src/librepgp/stream-dump.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_DUMP_H_ +#define STREAM_DUMP_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "json_object.h" +#include "json.h" +#include "rnp.h" +#include "stream-common.h" + +typedef struct rnp_dump_ctx_t { + bool dump_mpi; + bool dump_packets; + bool dump_grips; + size_t layers; + size_t stream_pkts; + size_t failures; +} rnp_dump_ctx_t; + +rnp_result_t stream_dump_packets(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst); + +rnp_result_t stream_dump_packets_json(rnp_dump_ctx_t *ctx, + pgp_source_t * src, + json_object ** jso); + +#endif diff --git a/src/librepgp/stream-key.cpp b/src/librepgp/stream-key.cpp new file mode 100644 index 0000000..8090ff7 --- /dev/null +++ b/src/librepgp/stream-key.cpp @@ -0,0 +1,1469 @@ +/* + * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include <time.h> +#include <inttypes.h> +#include "stream-def.h" +#include "stream-key.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "stream-sig.h" +#include "types.h" +#include "fingerprint.h" +#include "pgp-key.h" +#include "crypto.h" +#include "crypto/signatures.h" +#include "crypto/mem.h" +#include "../librekey/key_store_pgp.h" +#include <set> +#include <algorithm> +#include <cassert> + +/** + * @brief Add signatures from src to dst, skipping the duplicates. + * + * @param dst Vector which will contain all distinct signatures from src and dst + * @param src Vector to merge signatures from + * @return true on success or false otherwise. On failure dst may have some sigs appended. + */ +static rnp_result_t +merge_signatures(pgp_signature_list_t &dst, const pgp_signature_list_t &src) +{ + for (auto &sig : src) { + try { + if (std::find(dst.begin(), dst.end(), sig) != dst.end()) { + continue; + } + dst.emplace_back(sig); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} + +static rnp_result_t +transferable_userid_merge(pgp_transferable_userid_t &dst, const pgp_transferable_userid_t &src) +{ + if (dst.uid != src.uid) { + RNP_LOG("wrong userid merge attempt"); + return RNP_ERROR_BAD_PARAMETERS; + } + return merge_signatures(dst.signatures, src.signatures); +} + +rnp_result_t +transferable_subkey_from_key(pgp_transferable_subkey_t &dst, const pgp_key_t &key) +{ + try { + auto vec = rnp_key_to_vec(key); + rnp::MemorySource mem(vec); + return process_pgp_subkey(mem.src(), dst, false); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +rnp_result_t +transferable_subkey_merge(pgp_transferable_subkey_t &dst, const pgp_transferable_subkey_t &src) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (!dst.subkey.equals(src.subkey, true)) { + RNP_LOG("wrong subkey merge call"); + return RNP_ERROR_BAD_PARAMETERS; + } + if ((ret = merge_signatures(dst.signatures, src.signatures))) { + RNP_LOG("failed to merge signatures"); + } + return ret; +} + +rnp_result_t +transferable_key_from_key(pgp_transferable_key_t &dst, const pgp_key_t &key) +{ + try { + auto vec = rnp_key_to_vec(key); + rnp::MemorySource mem(vec); + return process_pgp_key(mem.src(), dst, false); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +static pgp_transferable_userid_t * +transferable_key_has_userid(pgp_transferable_key_t &src, const pgp_userid_pkt_t &userid) +{ + for (auto &uid : src.userids) { + if (uid.uid == userid) { + return &uid; + } + } + return NULL; +} + +static pgp_transferable_subkey_t * +transferable_key_has_subkey(pgp_transferable_key_t &src, const pgp_key_pkt_t &subkey) +{ + for (auto &srcsub : src.subkeys) { + if (srcsub.subkey.equals(subkey, true)) { + return &srcsub; + } + } + return NULL; +} + +rnp_result_t +transferable_key_merge(pgp_transferable_key_t &dst, const pgp_transferable_key_t &src) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (!dst.key.equals(src.key, true)) { + RNP_LOG("wrong key merge call"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* direct-key signatures */ + if ((ret = merge_signatures(dst.signatures, src.signatures))) { + RNP_LOG("failed to merge signatures"); + return ret; + } + /* userids */ + for (auto &srcuid : src.userids) { + pgp_transferable_userid_t *dstuid = transferable_key_has_userid(dst, srcuid.uid); + if (dstuid) { + if ((ret = transferable_userid_merge(*dstuid, srcuid))) { + RNP_LOG("failed to merge userid"); + return ret; + } + continue; + } + /* add userid */ + try { + dst.userids.emplace_back(srcuid); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + + /* subkeys */ + for (auto &srcsub : src.subkeys) { + pgp_transferable_subkey_t *dstsub = transferable_key_has_subkey(dst, srcsub.subkey); + if (dstsub) { + if ((ret = transferable_subkey_merge(*dstsub, srcsub))) { + RNP_LOG("failed to merge subkey"); + return ret; + } + continue; + } + /* add subkey */ + if (is_public_key_pkt(dst.key.tag) != is_public_key_pkt(srcsub.subkey.tag)) { + RNP_LOG("warning: adding public/secret subkey to secret/public key"); + } + try { + dst.subkeys.emplace_back(srcsub); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} + +static bool +skip_pgp_packets(pgp_source_t &src, const std::set<pgp_pkt_type_t> &pkts) +{ + do { + int pkt = stream_pkt_type(src); + if (!pkt) { + break; + } + if (pkt < 0) { + return false; + } + if (pkts.find((pgp_pkt_type_t) pkt) == pkts.end()) { + return true; + } + uint64_t ppos = src.readb; + if (stream_skip_packet(&src)) { + RNP_LOG("failed to skip packet at %" PRIu64, ppos); + return false; + } + } while (1); + + return true; +} + +static rnp_result_t +process_pgp_key_signatures(pgp_source_t &src, pgp_signature_list_t &sigs, bool skiperrors) +{ + int ptag; + while ((ptag = stream_pkt_type(src)) == PGP_PKT_SIGNATURE) { + uint64_t sigpos = src.readb; + try { + pgp_signature_t sig; + rnp_result_t ret = sig.parse(src); + if (ret) { + RNP_LOG("failed to parse signature at %" PRIu64, sigpos); + if (!skiperrors) { + return ret; + } + } else { + sigs.emplace_back(std::move(sig)); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + } + return ptag < 0 ? RNP_ERROR_BAD_FORMAT : RNP_SUCCESS; +} + +static rnp_result_t +process_pgp_userid(pgp_source_t &src, pgp_transferable_userid_t &uid, bool skiperrors) +{ + rnp_result_t ret; + uint64_t uidpos = src.readb; + try { + ret = uid.uid.parse(src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + RNP_LOG("failed to parse userid at %" PRIu64, uidpos); + return ret; + } + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + return process_pgp_key_signatures(src, uid.signatures, skiperrors); +} + +rnp_result_t +process_pgp_subkey(pgp_source_t &src, pgp_transferable_subkey_t &subkey, bool skiperrors) +{ + int ptag; + subkey = pgp_transferable_subkey_t(); + uint64_t keypos = src.readb; + if (!is_subkey_pkt(ptag = stream_pkt_type(src))) { + RNP_LOG("wrong subkey ptag: %d at %" PRIu64, ptag, keypos); + return RNP_ERROR_BAD_FORMAT; + } + + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + try { + ret = subkey.subkey.parse(src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + RNP_LOG("failed to parse subkey at %" PRIu64, keypos); + subkey.subkey = {}; + return ret; + } + + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + + return process_pgp_key_signatures(src, subkey.signatures, skiperrors); +} + +rnp_result_t +process_pgp_key_auto(pgp_source_t & src, + pgp_transferable_key_t &key, + bool allowsub, + bool skiperrors) +{ + key = {}; + uint64_t srcpos = src.readb; + int ptag = stream_pkt_type(src); + if (is_subkey_pkt(ptag) && allowsub) { + pgp_transferable_subkey_t subkey; + rnp_result_t ret = process_pgp_subkey(src, subkey, skiperrors); + if (subkey.subkey.tag != PGP_PKT_RESERVED) { + try { + key.subkeys.push_back(std::move(subkey)); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_OUT_OF_MEMORY; + } + } + /* change error code if we didn't process anything at all */ + if (srcpos == src.readb) { + ret = RNP_ERROR_BAD_STATE; + } + return ret; + } + + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + if (!is_primary_key_pkt(ptag)) { + RNP_LOG("wrong key tag: %d at pos %" PRIu64, ptag, src.readb); + } else { + try { + ret = process_pgp_key(src, key, skiperrors); + } catch (const rnp::rnp_exception &e) { + RNP_LOG("%s", e.what()); + ret = e.code(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + } + if (skiperrors && (ret == RNP_ERROR_BAD_FORMAT) && + !skip_pgp_packets(src, + {PGP_PKT_TRUST, + PGP_PKT_SIGNATURE, + PGP_PKT_USER_ID, + PGP_PKT_USER_ATTR, + PGP_PKT_PUBLIC_SUBKEY, + PGP_PKT_SECRET_SUBKEY})) { + ret = RNP_ERROR_READ; + } + /* change error code if we didn't process anything at all */ + if (srcpos == src.readb) { + ret = RNP_ERROR_BAD_STATE; + } + return ret; +} + +rnp_result_t +process_pgp_keys(pgp_source_t &src, pgp_key_sequence_t &keys, bool skiperrors) +{ + bool has_secret = false; + bool has_public = false; + + keys.keys.clear(); + /* create maybe-armored stream */ + rnp::ArmoredSource armor( + src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + + /* read sequence of transferable OpenPGP keys as described in RFC 4880, 11.1 - 11.2 */ + while (!armor.error()) { + /* Allow multiple armored messages in a single stream */ + if (armor.eof() && armor.multiple()) { + armor.restart(); + } + if (armor.eof()) { + break; + } + /* Attempt to read the next key */ + pgp_transferable_key_t curkey; + rnp_result_t ret = process_pgp_key_auto(armor.src(), curkey, false, skiperrors); + if (ret && (!skiperrors || (ret != RNP_ERROR_BAD_FORMAT))) { + keys.keys.clear(); + return ret; + } + /* check whether we actually read any key or just skipped erroneous packets */ + if (curkey.key.tag == PGP_PKT_RESERVED) { + continue; + } + has_secret |= (curkey.key.tag == PGP_PKT_SECRET_KEY); + has_public |= (curkey.key.tag == PGP_PKT_PUBLIC_KEY); + + keys.keys.emplace_back(std::move(curkey)); + } + + if (has_secret && has_public) { + RNP_LOG("warning! public keys are mixed together with secret ones!"); + } + + if (armor.error()) { + keys.keys.clear(); + return RNP_ERROR_READ; + } + return RNP_SUCCESS; +} + +rnp_result_t +process_pgp_key(pgp_source_t &src, pgp_transferable_key_t &key, bool skiperrors) +{ + key = pgp_transferable_key_t(); + /* create maybe-armored stream */ + rnp::ArmoredSource armor( + src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + + /* main key packet */ + uint64_t keypos = armor.readb(); + int ptag = stream_pkt_type(armor.src()); + if ((ptag <= 0) || !is_primary_key_pkt(ptag)) { + RNP_LOG("wrong key packet tag: %d at %" PRIu64, ptag, keypos); + return RNP_ERROR_BAD_FORMAT; + } + + rnp_result_t ret = key.key.parse(armor.src()); + if (ret) { + RNP_LOG("failed to parse key pkt at %" PRIu64, keypos); + key.key = {}; + return ret; + } + + if (!skip_pgp_packets(armor.src(), {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + + /* direct-key signatures */ + if ((ret = process_pgp_key_signatures(armor.src(), key.signatures, skiperrors))) { + return ret; + } + + /* user ids/attrs with signatures */ + while ((ptag = stream_pkt_type(armor.src())) > 0) { + if ((ptag != PGP_PKT_USER_ID) && (ptag != PGP_PKT_USER_ATTR)) { + break; + } + + pgp_transferable_userid_t uid; + ret = process_pgp_userid(armor.src(), uid, skiperrors); + if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors && + skip_pgp_packets(armor.src(), {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { + /* skip malformed uid */ + continue; + } + if (ret) { + return ret; + } + key.userids.push_back(std::move(uid)); + } + + /* subkeys with signatures */ + while ((ptag = stream_pkt_type(armor.src())) > 0) { + if (!is_subkey_pkt(ptag)) { + break; + } + + pgp_transferable_subkey_t subkey; + ret = process_pgp_subkey(armor.src(), subkey, skiperrors); + if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors && + skip_pgp_packets(armor.src(), {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { + /* skip malformed subkey */ + continue; + } + if (ret) { + return ret; + } + key.subkeys.emplace_back(std::move(subkey)); + } + return ptag >= 0 ? RNP_SUCCESS : RNP_ERROR_BAD_FORMAT; +} + +static rnp_result_t +decrypt_secret_key_v3(pgp_crypt_t *crypt, uint8_t *dec, const uint8_t *enc, size_t len) +{ + size_t idx; + size_t pos = 0; + size_t mpilen; + size_t blsize; + + if (!(blsize = pgp_cipher_block_size(crypt))) { + RNP_LOG("wrong crypto"); + return RNP_ERROR_BAD_STATE; + } + + /* 4 RSA secret mpis with cleartext header */ + for (idx = 0; idx < 4; idx++) { + if (pos + 2 > len) { + RNP_LOG("bad v3 secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + mpilen = (read_uint16(enc + pos) + 7) >> 3; + memcpy(dec + pos, enc + pos, 2); + pos += 2; + if (pos + mpilen > len) { + RNP_LOG("bad v3 secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + pgp_cipher_cfb_decrypt(crypt, dec + pos, enc + pos, mpilen); + pos += mpilen; + if (mpilen < blsize) { + RNP_LOG("bad rsa v3 mpi len"); + return RNP_ERROR_BAD_FORMAT; + } + pgp_cipher_cfb_resync(crypt, enc + pos - blsize); + } + + /* sum16 */ + if (pos + 2 != len) { + return RNP_ERROR_BAD_FORMAT; + } + memcpy(dec + pos, enc + pos, 2); + return RNP_SUCCESS; +} + +static rnp_result_t +parse_secret_key_mpis(pgp_key_pkt_t &key, const uint8_t *mpis, size_t len) +{ + if (!mpis) { + return RNP_ERROR_NULL_POINTER; + } + + /* check the cleartext data */ + switch (key.sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + case PGP_S2KU_ENCRYPTED: { + /* calculate and check sum16 of the cleartext */ + if (len < 2) { + RNP_LOG("No space for checksum."); + return RNP_ERROR_BAD_FORMAT; + } + uint16_t sum = 0; + len -= 2; + for (size_t idx = 0; idx < len; idx++) { + sum += mpis[idx]; + } + uint16_t expsum = read_uint16(mpis + len); + if (sum != expsum) { + RNP_LOG("Wrong key checksum, got 0x%X instead of 0x%X.", (int) sum, (int) expsum); + return RNP_ERROR_DECRYPT_FAILED; + } + break; + } + case PGP_S2KU_ENCRYPTED_AND_HASHED: { + if (len < PGP_SHA1_HASH_SIZE) { + RNP_LOG("No space for hash"); + return RNP_ERROR_BAD_FORMAT; + } + /* calculate and check sha1 hash of the cleartext */ + uint8_t hval[PGP_SHA1_HASH_SIZE]; + try { + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + assert(hash->size() == sizeof(hval)); + len -= PGP_SHA1_HASH_SIZE; + hash->add(mpis, len); + if (hash->finish(hval) != PGP_SHA1_HASH_SIZE) { + return RNP_ERROR_BAD_STATE; + } + } catch (const std::exception &e) { + RNP_LOG("hash calculation failed: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } + if (memcmp(hval, mpis + len, PGP_SHA1_HASH_SIZE)) { + return RNP_ERROR_DECRYPT_FAILED; + } + break; + } + default: + RNP_LOG("unknown s2k usage: %d", (int) key.sec_protection.s2k.usage); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + /* parse mpis depending on algorithm */ + pgp_packet_body_t body(mpis, len); + + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!body.get(key.material.rsa.d) || !body.get(key.material.rsa.p) || + !body.get(key.material.rsa.q) || !body.get(key.material.rsa.u)) { + RNP_LOG("failed to parse rsa secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_DSA: + if (!body.get(key.material.dsa.x)) { + RNP_LOG("failed to parse dsa secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + if (!body.get(key.material.ec.x)) { + RNP_LOG("failed to parse ecc secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!body.get(key.material.eg.x)) { + RNP_LOG("failed to parse eg secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + default: + RNP_LOG("unknown pk alg : %d", (int) key.alg); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (body.left()) { + RNP_LOG("extra data in sec key"); + return RNP_ERROR_BAD_FORMAT; + } + key.material.secret = true; + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +rnp_result_t +decrypt_secret_key(pgp_key_pkt_t *key, const char *password) +{ + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + if (!is_secret_key_pkt(key->tag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* mark material as not validated as it may be valid for public part */ + key->material.validity.reset(); + + /* check whether data is not encrypted */ + if (!key->sec_protection.s2k.usage) { + return parse_secret_key_mpis(*key, key->sec_data, key->sec_len); + } + + /* check whether secret key data present */ + if (!key->sec_len) { + RNP_LOG("No secret key data"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* data is encrypted */ + if (!password) { + return RNP_ERROR_NULL_POINTER; + } + + if (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB) { + RNP_LOG("unsupported secret key encryption mode"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> keybuf; + size_t keysize = pgp_key_size(key->sec_protection.symm_alg); + if (!keysize || + !pgp_s2k_derive_key(&key->sec_protection.s2k, password, keybuf.data(), keysize)) { + RNP_LOG("failed to derive key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + rnp::secure_vector<uint8_t> decdata(key->sec_len); + pgp_crypt_t crypt; + if (!pgp_cipher_cfb_start( + &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) { + RNP_LOG("failed to start cfb decryption"); + return RNP_ERROR_DECRYPT_FAILED; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + switch (key->version) { + case PGP_V3: + if (!is_rsa_key_alg(key->alg)) { + RNP_LOG("non-RSA v3 key"); + ret = RNP_ERROR_BAD_PARAMETERS; + break; + } + ret = decrypt_secret_key_v3(&crypt, decdata.data(), key->sec_data, key->sec_len); + break; + case PGP_V4: + pgp_cipher_cfb_decrypt(&crypt, decdata.data(), key->sec_data, key->sec_len); + ret = RNP_SUCCESS; + break; + default: + ret = RNP_ERROR_BAD_PARAMETERS; + } + + pgp_cipher_cfb_finish(&crypt); + if (ret) { + return ret; + } + + return parse_secret_key_mpis(*key, decdata.data(), key->sec_len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +static void +write_secret_key_mpis(pgp_packet_body_t &body, pgp_key_pkt_t &key) +{ + /* add mpis */ + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + body.add(key.material.rsa.d); + body.add(key.material.rsa.p); + body.add(key.material.rsa.q); + body.add(key.material.rsa.u); + break; + case PGP_PKA_DSA: + body.add(key.material.dsa.x); + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + body.add(key.material.ec.x); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + body.add(key.material.eg.x); + break; + default: + RNP_LOG("unknown pk alg : %d", (int) key.alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + /* add sum16 if sha1 is not used */ + if (key.sec_protection.s2k.usage != PGP_S2KU_ENCRYPTED_AND_HASHED) { + uint16_t sum = 0; + for (size_t i = 0; i < body.size(); i++) { + sum += body.data()[i]; + } + body.add_uint16(sum); + return; + } + + /* add sha1 hash */ + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + hash->add(body.data(), body.size()); + uint8_t hval[PGP_SHA1_HASH_SIZE]; + assert(sizeof(hval) == hash->size()); + if (hash->finish(hval) != PGP_SHA1_HASH_SIZE) { + RNP_LOG("failed to finish hash"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + body.add(hval, PGP_SHA1_HASH_SIZE); +} + +rnp_result_t +encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng) +{ + if (!is_secret_key_pkt(key->tag) || !key->material.secret) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (key->sec_protection.s2k.usage && + (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB)) { + RNP_LOG("unsupported secret key encryption mode"); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + /* build secret key data */ + pgp_packet_body_t body(PGP_PKT_RESERVED); + body.mark_secure(); + write_secret_key_mpis(body, *key); + + /* check whether data is not encrypted */ + if (key->sec_protection.s2k.usage == PGP_S2KU_NONE) { + secure_clear(key->sec_data, key->sec_len); + free(key->sec_data); + key->sec_data = (uint8_t *) malloc(body.size()); + if (!key->sec_data) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(key->sec_data, body.data(), body.size()); + key->sec_len = body.size(); + return RNP_SUCCESS; + } + if (key->version < PGP_V4) { + RNP_LOG("encryption of v3 keys is not supported"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* data is encrypted */ + size_t keysize = pgp_key_size(key->sec_protection.symm_alg); + size_t blsize = pgp_block_size(key->sec_protection.symm_alg); + if (!keysize || !blsize) { + RNP_LOG("wrong symm alg"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* generate iv and s2k salt */ + rng.get(key->sec_protection.iv, blsize); + if ((key->sec_protection.s2k.specifier != PGP_S2KS_SIMPLE)) { + rng.get(key->sec_protection.s2k.salt, PGP_SALT_SIZE); + } + /* derive key */ + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> keybuf; + if (!pgp_s2k_derive_key(&key->sec_protection.s2k, password, keybuf.data(), keysize)) { + RNP_LOG("failed to derive key"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* encrypt sec data */ + pgp_crypt_t crypt; + if (!pgp_cipher_cfb_start( + &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) { + RNP_LOG("failed to start cfb encryption"); + return RNP_ERROR_DECRYPT_FAILED; + } + pgp_cipher_cfb_encrypt(&crypt, body.data(), body.data(), body.size()); + pgp_cipher_cfb_finish(&crypt); + secure_clear(key->sec_data, key->sec_len); + free(key->sec_data); + key->sec_data = (uint8_t *) malloc(body.size()); + if (!key->sec_data) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(key->sec_data, body.data(), body.size()); + key->sec_len = body.size(); + /* cleanup cleartext fields */ + forget_secret_key_fields(&key->material); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +void +forget_secret_key_fields(pgp_key_material_t *key) +{ + if (!key || !key->secret) { + return; + } + + switch (key->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + mpi_forget(&key->rsa.d); + mpi_forget(&key->rsa.p); + mpi_forget(&key->rsa.q); + mpi_forget(&key->rsa.u); + break; + case PGP_PKA_DSA: + mpi_forget(&key->dsa.x); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + mpi_forget(&key->eg.x); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + mpi_forget(&key->ec.x); + break; + default: + RNP_LOG("unknown key algorithm: %d", (int) key->alg); + } + + key->secret = false; +} + +pgp_userid_pkt_t::pgp_userid_pkt_t(const pgp_userid_pkt_t &src) +{ + tag = src.tag; + uid_len = src.uid_len; + uid = NULL; + if (src.uid) { + uid = (uint8_t *) malloc(uid_len); + if (!uid) { + throw std::bad_alloc(); + } + memcpy(uid, src.uid, uid_len); + } +} + +pgp_userid_pkt_t::pgp_userid_pkt_t(pgp_userid_pkt_t &&src) +{ + tag = src.tag; + uid_len = src.uid_len; + uid = src.uid; + src.uid = NULL; +} + +pgp_userid_pkt_t & +pgp_userid_pkt_t::operator=(pgp_userid_pkt_t &&src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + uid_len = src.uid_len; + free(uid); + uid = src.uid; + src.uid = NULL; + return *this; +} + +pgp_userid_pkt_t & +pgp_userid_pkt_t::operator=(const pgp_userid_pkt_t &src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + uid_len = src.uid_len; + free(uid); + uid = NULL; + if (src.uid) { + uid = (uint8_t *) malloc(uid_len); + if (!uid) { + throw std::bad_alloc(); + } + memcpy(uid, src.uid, uid_len); + } + return *this; +} + +bool +pgp_userid_pkt_t::operator==(const pgp_userid_pkt_t &src) const +{ + return (tag == src.tag) && (uid_len == src.uid_len) && !memcmp(uid, src.uid, uid_len); +} + +bool +pgp_userid_pkt_t::operator!=(const pgp_userid_pkt_t &src) const +{ + return !(*this == src); +} + +pgp_userid_pkt_t::~pgp_userid_pkt_t() +{ + free(uid); +} + +void +pgp_userid_pkt_t::write(pgp_dest_t &dst) const +{ + if ((tag != PGP_PKT_USER_ID) && (tag != PGP_PKT_USER_ATTR)) { + RNP_LOG("wrong userid tag"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (uid_len && !uid) { + RNP_LOG("null but non-empty userid"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t pktbody(tag); + if (uid) { + pktbody.add(uid, uid_len); + } + pktbody.write(dst); +} + +rnp_result_t +pgp_userid_pkt_t::parse(pgp_source_t &src) +{ + /* check the tag */ + int stag = stream_pkt_type(src); + if ((stag != PGP_PKT_USER_ID) && (stag != PGP_PKT_USER_ATTR)) { + RNP_LOG("wrong userid tag: %d", stag); + return RNP_ERROR_BAD_FORMAT; + } + + pgp_packet_body_t pkt(PGP_PKT_RESERVED); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + + /* userid type, i.e. tag */ + tag = (pgp_pkt_type_t) stag; + free(uid); + uid = (uint8_t *) malloc(pkt.size()); + if (!uid) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(uid, pkt.data(), pkt.size()); + uid_len = pkt.size(); + return RNP_SUCCESS; +} + +pgp_key_pkt_t::pgp_key_pkt_t(const pgp_key_pkt_t &src, bool pubonly) +{ + if (pubonly && is_secret_key_pkt(src.tag)) { + tag = (src.tag == PGP_PKT_SECRET_KEY) ? PGP_PKT_PUBLIC_KEY : PGP_PKT_PUBLIC_SUBKEY; + } else { + tag = src.tag; + } + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + hashed_data = NULL; + if (src.hashed_data) { + hashed_data = (uint8_t *) malloc(hashed_len); + if (!hashed_data) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material = src.material; + if (pubonly) { + forget_secret_key_fields(&material); + sec_len = 0; + sec_data = NULL; + sec_protection = {}; + return; + } + sec_len = src.sec_len; + sec_data = NULL; + if (src.sec_data) { + sec_data = (uint8_t *) malloc(sec_len); + if (!sec_data) { + free(hashed_data); + hashed_data = NULL; + throw std::bad_alloc(); + } + memcpy(sec_data, src.sec_data, sec_len); + } + sec_protection = src.sec_protection; +} + +pgp_key_pkt_t::pgp_key_pkt_t(pgp_key_pkt_t &&src) +{ + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material = src.material; + forget_secret_key_fields(&src.material); + sec_len = src.sec_len; + sec_data = src.sec_data; + src.sec_data = NULL; + sec_protection = src.sec_protection; +} + +pgp_key_pkt_t & +pgp_key_pkt_t::operator=(pgp_key_pkt_t &&src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material = src.material; + forget_secret_key_fields(&src.material); + secure_clear(sec_data, sec_len); + free(sec_data); + sec_len = src.sec_len; + sec_data = src.sec_data; + src.sec_data = NULL; + src.sec_len = 0; + sec_protection = src.sec_protection; + return *this; +} + +pgp_key_pkt_t & +pgp_key_pkt_t::operator=(const pgp_key_pkt_t &src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = NULL; + if (src.hashed_data) { + hashed_data = (uint8_t *) malloc(hashed_len); + if (!hashed_data) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material = src.material; + secure_clear(sec_data, sec_len); + free(sec_data); + sec_data = NULL; + sec_len = src.sec_len; + if (src.sec_data) { + sec_data = (uint8_t *) malloc(sec_len); + if (!sec_data) { + free(hashed_data); + hashed_data = NULL; + throw std::bad_alloc(); + } + memcpy(sec_data, src.sec_data, sec_len); + } + sec_protection = src.sec_protection; + return *this; +} + +pgp_key_pkt_t::~pgp_key_pkt_t() +{ + forget_secret_key_fields(&material); + free(hashed_data); + secure_clear(sec_data, sec_len); + free(sec_data); +} + +void +pgp_key_pkt_t::write(pgp_dest_t &dst) +{ + if (!is_key_pkt(tag)) { + RNP_LOG("wrong key tag"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (!hashed_data) { + fill_hashed_data(); + } + + pgp_packet_body_t pktbody(tag); + /* all public key data is written in hashed_data */ + pktbody.add(hashed_data, hashed_len); + /* if we have public key then we do not need further processing */ + if (!is_secret_key_pkt(tag)) { + pktbody.write(dst); + return; + } + + /* secret key fields should be pre-populated in sec_data field */ + if ((sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) && (!sec_data || !sec_len)) { + RNP_LOG("secret key data is not populated"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + pktbody.add_byte(sec_protection.s2k.usage); + + switch (sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + break; + case PGP_S2KU_ENCRYPTED_AND_HASHED: + case PGP_S2KU_ENCRYPTED: { + pktbody.add_byte(sec_protection.symm_alg); + pktbody.add(sec_protection.s2k); + if (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) { + size_t blsize = pgp_block_size(sec_protection.symm_alg); + if (!blsize) { + RNP_LOG("wrong block size"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + pktbody.add(sec_protection.iv, blsize); + } + break; + } + default: + RNP_LOG("wrong s2k usage"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (sec_len) { + /* if key is stored on card, or exported via gpg --export-secret-subkeys, then + * sec_data is empty */ + pktbody.add(sec_data, sec_len); + } + pktbody.write(dst); +} + +rnp_result_t +pgp_key_pkt_t::parse(pgp_source_t &src) +{ + /* check the key tag */ + int atag = stream_pkt_type(src); + if (!is_key_pkt(atag)) { + RNP_LOG("wrong key packet tag: %d", atag); + return RNP_ERROR_BAD_FORMAT; + } + + pgp_packet_body_t pkt((pgp_pkt_type_t) atag); + /* Read the packet into memory */ + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + /* key type, i.e. tag */ + tag = (pgp_pkt_type_t) atag; + /* version */ + uint8_t ver = 0; + if (!pkt.get(ver) || (ver < PGP_V2) || (ver > PGP_V4)) { + RNP_LOG("wrong key packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = (pgp_version_t) ver; + /* creation time */ + if (!pkt.get(creation_time)) { + return RNP_ERROR_BAD_FORMAT; + } + /* v3: validity days */ + if ((version < PGP_V4) && !pkt.get(v3_days)) { + return RNP_ERROR_BAD_FORMAT; + } + /* key algorithm */ + uint8_t analg = 0; + if (!pkt.get(analg)) { + return RNP_ERROR_BAD_FORMAT; + } + alg = (pgp_pubkey_alg_t) analg; + material.alg = (pgp_pubkey_alg_t) analg; + /* v3 keys must be RSA-only */ + if ((version < PGP_V4) && !is_rsa_key_alg(alg)) { + RNP_LOG("wrong v3 pk algorithm"); + return RNP_ERROR_BAD_FORMAT; + } + /* algorithm specific fields */ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!pkt.get(material.rsa.n) || !pkt.get(material.rsa.e)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_DSA: + if (!pkt.get(material.dsa.p) || !pkt.get(material.dsa.q) || !pkt.get(material.dsa.g) || + !pkt.get(material.dsa.y)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!pkt.get(material.eg.p) || !pkt.get(material.eg.g) || !pkt.get(material.eg.y)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + if (!pkt.get(material.ec.curve) || !pkt.get(material.ec.p)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ECDH: { + if (!pkt.get(material.ec.curve) || !pkt.get(material.ec.p)) { + return RNP_ERROR_BAD_FORMAT; + } + /* read KDF parameters. At the moment should be 0x03 0x01 halg ealg */ + uint8_t len = 0, halg = 0, walg = 0; + if (!pkt.get(len) || (len != 3)) { + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(len) || (len != 1)) { + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(halg) || !pkt.get(walg)) { + return RNP_ERROR_BAD_FORMAT; + } + material.ec.kdf_hash_alg = (pgp_hash_alg_t) halg; + material.ec.key_wrap_alg = (pgp_symm_alg_t) walg; + break; + } + default: + RNP_LOG("unknown key algorithm: %d", (int) alg); + return RNP_ERROR_BAD_FORMAT; + } + /* fill hashed data used for signatures */ + if (!(hashed_data = (uint8_t *) malloc(pkt.size() - pkt.left()))) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(hashed_data, pkt.data(), pkt.size() - pkt.left()); + hashed_len = pkt.size() - pkt.left(); + + /* secret key fields if any */ + if (is_secret_key_pkt(tag)) { + uint8_t usage = 0; + if (!pkt.get(usage)) { + RNP_LOG("failed to read key protection"); + return RNP_ERROR_BAD_FORMAT; + } + sec_protection.s2k.usage = (pgp_s2k_usage_t) usage; + sec_protection.cipher_mode = PGP_CIPHER_MODE_CFB; + + switch (sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + break; + case PGP_S2KU_ENCRYPTED: + case PGP_S2KU_ENCRYPTED_AND_HASHED: { + /* we have s2k */ + uint8_t salg = 0; + if (!pkt.get(salg) || !pkt.get(sec_protection.s2k)) { + RNP_LOG("failed to read key protection"); + return RNP_ERROR_BAD_FORMAT; + } + sec_protection.symm_alg = (pgp_symm_alg_t) salg; + break; + } + default: + /* old-style: usage is symmetric algorithm identifier */ + sec_protection.symm_alg = (pgp_symm_alg_t) usage; + sec_protection.s2k.usage = PGP_S2KU_ENCRYPTED; + sec_protection.s2k.specifier = PGP_S2KS_SIMPLE; + sec_protection.s2k.hash_alg = PGP_HASH_MD5; + break; + } + + /* iv */ + if (sec_protection.s2k.usage && + (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + size_t bl_size = pgp_block_size(sec_protection.symm_alg); + if (!bl_size || !pkt.get(sec_protection.iv, bl_size)) { + RNP_LOG("failed to read iv"); + return RNP_ERROR_BAD_FORMAT; + } + } + + /* encrypted/cleartext secret MPIs are left */ + size_t asec_len = pkt.left(); + if (!asec_len) { + sec_data = NULL; + } else { + if (!(sec_data = (uint8_t *) calloc(1, asec_len))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!pkt.get(sec_data, asec_len)) { + return RNP_ERROR_BAD_STATE; + } + } + sec_len = asec_len; + } + + if (pkt.left()) { + RNP_LOG("extra %d bytes in key packet", (int) pkt.left()); + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +void +pgp_key_pkt_t::fill_hashed_data() +{ + /* we don't have a need to write v2-v3 signatures */ + if (version != PGP_V4) { + RNP_LOG("unknown key version %d", (int) version); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t hbody(PGP_PKT_RESERVED); + hbody.add_byte(version); + hbody.add_uint32(creation_time); + hbody.add_byte(alg); + /* Algorithm specific fields */ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + hbody.add(material.rsa.n); + hbody.add(material.rsa.e); + break; + case PGP_PKA_DSA: + hbody.add(material.dsa.p); + hbody.add(material.dsa.q); + hbody.add(material.dsa.g); + hbody.add(material.dsa.y); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + hbody.add(material.eg.p); + hbody.add(material.eg.g); + hbody.add(material.eg.y); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + hbody.add(material.ec.curve); + hbody.add(material.ec.p); + break; + case PGP_PKA_ECDH: + hbody.add(material.ec.curve); + hbody.add(material.ec.p); + hbody.add_byte(3); + hbody.add_byte(1); + hbody.add_byte(material.ec.kdf_hash_alg); + hbody.add_byte(material.ec.key_wrap_alg); + break; + default: + RNP_LOG("unknown key algorithm: %d", (int) alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + hashed_data = (uint8_t *) malloc(hbody.size()); + if (!hashed_data) { + RNP_LOG("allocation failed"); + throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + memcpy(hashed_data, hbody.data(), hbody.size()); + hashed_len = hbody.size(); +} + +bool +pgp_key_pkt_t::equals(const pgp_key_pkt_t &key, bool pubonly) const noexcept +{ + /* check tag. We allow public/secret key comparison here */ + if (pubonly) { + if (is_subkey_pkt(tag) && !is_subkey_pkt(key.tag)) { + return false; + } + if (is_key_pkt(tag) && !is_key_pkt(key.tag)) { + return false; + } + } else if (tag != key.tag) { + return false; + } + /* check basic fields */ + if ((version != key.version) || (alg != key.alg) || (creation_time != key.creation_time)) { + return false; + } + /* check key material */ + return key_material_equal(&material, &key.material); +} + +pgp_transferable_subkey_t::pgp_transferable_subkey_t(const pgp_transferable_subkey_t &src, + bool pubonly) +{ + subkey = pgp_key_pkt_t(src.subkey, pubonly); + signatures = src.signatures; +} + +pgp_transferable_key_t::pgp_transferable_key_t(const pgp_transferable_key_t &src, bool pubonly) +{ + key = pgp_key_pkt_t(src.key, pubonly); + userids = src.userids; + subkeys = src.subkeys; + signatures = src.signatures; +} diff --git a/src/librepgp/stream-key.h b/src/librepgp/stream-key.h new file mode 100644 index 0000000..a19a986 --- /dev/null +++ b/src/librepgp/stream-key.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_KEY_H_ +#define STREAM_KEY_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "rnp.h" +#include "stream-common.h" +#include "stream-sig.h" +#include "stream-packet.h" + +/** Struct to hold a key packet. May contain public or private key/subkey */ +typedef struct pgp_key_pkt_t { + pgp_pkt_type_t tag; /* packet tag: public key/subkey or private key/subkey */ + pgp_version_t version; /* Key packet version */ + uint32_t creation_time; /* Key creation time */ + pgp_pubkey_alg_t alg; + uint16_t v3_days; /* v2/v3 validity time */ + + uint8_t *hashed_data; /* key's hashed data used for signature calculation */ + size_t hashed_len; + + pgp_key_material_t material; + + /* secret key data, if available. sec_len == 0, sec_data == NULL for public key/subkey */ + pgp_key_protection_t sec_protection; + uint8_t * sec_data; + size_t sec_len; + + pgp_key_pkt_t() + : tag(PGP_PKT_RESERVED), version(PGP_VUNKNOWN), creation_time(0), alg(PGP_PKA_NOTHING), + v3_days(0), hashed_data(NULL), hashed_len(0), material({}), sec_protection({}), + sec_data(NULL), sec_len(0){}; + pgp_key_pkt_t(const pgp_key_pkt_t &src, bool pubonly = false); + pgp_key_pkt_t(pgp_key_pkt_t &&src); + pgp_key_pkt_t &operator=(pgp_key_pkt_t &&src); + pgp_key_pkt_t &operator=(const pgp_key_pkt_t &src); + ~pgp_key_pkt_t(); + + void write(pgp_dest_t &dst); + rnp_result_t parse(pgp_source_t &src); + /** @brief Fills the hashed (signed) data part of the key packet. Must be called before + * pgp_key_pkt_t::write() on the newly generated key */ + void fill_hashed_data(); + bool equals(const pgp_key_pkt_t &key, bool pubonly = false) const noexcept; +} pgp_key_pkt_t; + +/* userid/userattr with all the corresponding signatures */ +typedef struct pgp_transferable_userid_t { + pgp_userid_pkt_t uid; + pgp_signature_list_t signatures; +} pgp_transferable_userid_t; + +/* subkey with all corresponding signatures */ +typedef struct pgp_transferable_subkey_t { + pgp_key_pkt_t subkey; + pgp_signature_list_t signatures; + + pgp_transferable_subkey_t() = default; + pgp_transferable_subkey_t(const pgp_transferable_subkey_t &src, bool pubonly = false); + pgp_transferable_subkey_t &operator=(const pgp_transferable_subkey_t &) = default; +} pgp_transferable_subkey_t; + +/* transferable key with userids, subkeys and revocation signatures */ +typedef struct pgp_transferable_key_t { + pgp_key_pkt_t key; /* main key packet */ + std::vector<pgp_transferable_userid_t> userids; + std::vector<pgp_transferable_subkey_t> subkeys; + pgp_signature_list_t signatures; + + pgp_transferable_key_t() = default; + pgp_transferable_key_t(const pgp_transferable_key_t &src, bool pubonly = false); + pgp_transferable_key_t &operator=(const pgp_transferable_key_t &) = default; +} pgp_transferable_key_t; + +/* sequence of OpenPGP transferable keys */ +typedef struct pgp_key_sequence_t { + std::vector<pgp_transferable_key_t> keys; +} pgp_key_sequence_t; + +rnp_result_t transferable_key_from_key(pgp_transferable_key_t &dst, const pgp_key_t &key); + +rnp_result_t transferable_key_merge(pgp_transferable_key_t & dst, + const pgp_transferable_key_t &src); + +rnp_result_t transferable_subkey_from_key(pgp_transferable_subkey_t &dst, + const pgp_key_t & key); + +rnp_result_t transferable_subkey_merge(pgp_transferable_subkey_t & dst, + const pgp_transferable_subkey_t &src); + +/* Process single primary key or subkey, skipping all key-related packets on error. + If key.key.tag is zero, then (on success) result is subkey and it is stored in + key.subkeys[0]. + If returns RNP_ERROR_BAD_FORMAT then some packets failed parsing, but still key may contain + successfully read key or subkey. +*/ +rnp_result_t process_pgp_key_auto(pgp_source_t & src, + pgp_transferable_key_t &key, + bool allowsub, + bool skiperrors); + +rnp_result_t process_pgp_keys(pgp_source_t &src, pgp_key_sequence_t &keys, bool skiperrors); + +rnp_result_t process_pgp_key(pgp_source_t &src, pgp_transferable_key_t &key, bool skiperrors); + +rnp_result_t process_pgp_subkey(pgp_source_t & src, + pgp_transferable_subkey_t &subkey, + bool skiperrors); + +rnp_result_t decrypt_secret_key(pgp_key_pkt_t *key, const char *password); + +rnp_result_t encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng); + +void forget_secret_key_fields(pgp_key_material_t *key); + +#endif diff --git a/src/librepgp/stream-packet.cpp b/src/librepgp/stream-packet.cpp new file mode 100644 index 0000000..49dd63d --- /dev/null +++ b/src/librepgp/stream-packet.cpp @@ -0,0 +1,1228 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include <inttypes.h> +#include <rnp/rnp_def.h> +#include "types.h" +#include "crypto.h" +#include "crypto/mem.h" +#include "stream-packet.h" +#include "stream-key.h" +#include <algorithm> + +uint32_t +read_uint32(const uint8_t *buf) +{ + return ((uint32_t) buf[0] << 24) | ((uint32_t) buf[1] << 16) | ((uint32_t) buf[2] << 8) | + (uint32_t) buf[3]; +} + +uint16_t +read_uint16(const uint8_t *buf) +{ + return ((uint16_t) buf[0] << 8) | buf[1]; +} + +void +write_uint16(uint8_t *buf, uint16_t val) +{ + buf[0] = val >> 8; + buf[1] = val & 0xff; +} + +size_t +write_packet_len(uint8_t *buf, size_t len) +{ + if (len < 192) { + buf[0] = len; + return 1; + } else if (len < 8192 + 192) { + buf[0] = ((len - 192) >> 8) + 192; + buf[1] = (len - 192) & 0xff; + return 2; + } else { + buf[0] = 0xff; + STORE32BE(&buf[1], len); + return 5; + } +} + +int +get_packet_type(uint8_t ptag) +{ + if (!(ptag & PGP_PTAG_ALWAYS_SET)) { + return -1; + } + + if (ptag & PGP_PTAG_NEW_FORMAT) { + return (int) (ptag & PGP_PTAG_NF_CONTENT_TAG_MASK); + } else { + return (int) ((ptag & PGP_PTAG_OF_CONTENT_TAG_MASK) >> PGP_PTAG_OF_CONTENT_TAG_SHIFT); + } +} + +int +stream_pkt_type(pgp_source_t &src) +{ + if (src_eof(&src)) { + return 0; + } + size_t hdrneed = 0; + if (!stream_pkt_hdr_len(src, hdrneed)) { + return -1; + } + uint8_t hdr[PGP_MAX_HEADER_SIZE]; + if (!src_peek_eq(&src, hdr, hdrneed)) { + return -1; + } + return get_packet_type(hdr[0]); +} + +bool +stream_pkt_hdr_len(pgp_source_t &src, size_t &hdrlen) +{ + uint8_t buf[2]; + + if (!src_peek_eq(&src, buf, 2) || !(buf[0] & PGP_PTAG_ALWAYS_SET)) { + return false; + } + + if (buf[0] & PGP_PTAG_NEW_FORMAT) { + if (buf[1] < 192) { + hdrlen = 2; + } else if (buf[1] < 224) { + hdrlen = 3; + } else if (buf[1] < 255) { + hdrlen = 2; + } else { + hdrlen = 6; + } + return true; + } + + switch (buf[0] & PGP_PTAG_OF_LENGTH_TYPE_MASK) { + case PGP_PTAG_OLD_LEN_1: + hdrlen = 2; + return true; + case PGP_PTAG_OLD_LEN_2: + hdrlen = 3; + return true; + case PGP_PTAG_OLD_LEN_4: + hdrlen = 5; + return true; + case PGP_PTAG_OLD_LEN_INDETERMINATE: + hdrlen = 1; + return true; + default: + return false; + } +} + +static bool +get_pkt_len(uint8_t *hdr, size_t *pktlen) +{ + if (hdr[0] & PGP_PTAG_NEW_FORMAT) { + // 1-byte length + if (hdr[1] < 192) { + *pktlen = hdr[1]; + return true; + } + // 2-byte length + if (hdr[1] < 224) { + *pktlen = ((size_t)(hdr[1] - 192) << 8) + (size_t) hdr[2] + 192; + return true; + } + // partial length - we do not allow it here + if (hdr[1] < 255) { + return false; + } + // 4-byte length + *pktlen = read_uint32(&hdr[2]); + return true; + } + + switch (hdr[0] & PGP_PTAG_OF_LENGTH_TYPE_MASK) { + case PGP_PTAG_OLD_LEN_1: + *pktlen = hdr[1]; + return true; + case PGP_PTAG_OLD_LEN_2: + *pktlen = read_uint16(&hdr[1]); + return true; + case PGP_PTAG_OLD_LEN_4: + *pktlen = read_uint32(&hdr[1]); + return true; + default: + return false; + } +} + +bool +stream_read_pkt_len(pgp_source_t *src, size_t *pktlen) +{ + uint8_t buf[6] = {}; + size_t read = 0; + + if (!stream_pkt_hdr_len(*src, read)) { + return false; + } + + if (!src_read_eq(src, buf, read)) { + return false; + } + + return get_pkt_len(buf, pktlen); +} + +bool +stream_read_partial_chunk_len(pgp_source_t *src, size_t *clen, bool *last) +{ + uint8_t hdr[5] = {}; + size_t read = 0; + + if (!src_read(src, hdr, 1, &read)) { + RNP_LOG("failed to read header"); + return false; + } + if (read < 1) { + RNP_LOG("wrong eof"); + return false; + } + + *last = true; + // partial length + if ((hdr[0] >= 224) && (hdr[0] < 255)) { + *last = false; + *clen = get_partial_pkt_len(hdr[0]); + return true; + } + // 1-byte length + if (hdr[0] < 192) { + *clen = hdr[0]; + return true; + } + // 2-byte length + if (hdr[0] < 224) { + if (!src_read_eq(src, &hdr[1], 1)) { + RNP_LOG("wrong 2-byte length"); + return false; + } + *clen = ((size_t)(hdr[0] - 192) << 8) + (size_t) hdr[1] + 192; + return true; + } + // 4-byte length + if (!src_read_eq(src, &hdr[1], 4)) { + RNP_LOG("wrong 4-byte length"); + return false; + } + *clen = ((size_t) hdr[1] << 24) | ((size_t) hdr[2] << 16) | ((size_t) hdr[3] << 8) | + (size_t) hdr[4]; + return true; +} + +bool +stream_old_indeterminate_pkt_len(pgp_source_t *src) +{ + uint8_t ptag = 0; + if (!src_peek_eq(src, &ptag, 1)) { + return false; + } + return !(ptag & PGP_PTAG_NEW_FORMAT) && + ((ptag & PGP_PTAG_OF_LENGTH_TYPE_MASK) == PGP_PTAG_OLD_LEN_INDETERMINATE); +} + +bool +stream_partial_pkt_len(pgp_source_t *src) +{ + uint8_t hdr[2] = {}; + if (!src_peek_eq(src, hdr, 2)) { + return false; + } + return (hdr[0] & PGP_PTAG_NEW_FORMAT) && (hdr[1] >= 224) && (hdr[1] < 255); +} + +size_t +get_partial_pkt_len(uint8_t blen) +{ + return 1 << (blen & 0x1f); +} + +rnp_result_t +stream_peek_packet_hdr(pgp_source_t *src, pgp_packet_hdr_t *hdr) +{ + size_t hlen = 0; + memset(hdr, 0, sizeof(*hdr)); + if (!stream_pkt_hdr_len(*src, hlen)) { + uint8_t hdr2[2] = {0}; + if (!src_peek_eq(src, hdr2, 2)) { + RNP_LOG("pkt header read failed"); + return RNP_ERROR_READ; + } + + RNP_LOG("bad packet header: 0x%02x%02x", hdr2[0], hdr2[1]); + return RNP_ERROR_BAD_FORMAT; + } + + if (!src_peek_eq(src, hdr->hdr, hlen)) { + RNP_LOG("failed to read pkt header"); + return RNP_ERROR_READ; + } + + hdr->hdr_len = hlen; + hdr->tag = (pgp_pkt_type_t) get_packet_type(hdr->hdr[0]); + + if (stream_partial_pkt_len(src)) { + hdr->partial = true; + } else if (stream_old_indeterminate_pkt_len(src)) { + hdr->indeterminate = true; + } else { + (void) get_pkt_len(hdr->hdr, &hdr->pkt_len); + } + + return RNP_SUCCESS; +} + +static rnp_result_t +stream_read_packet_partial(pgp_source_t *src, pgp_dest_t *dst) +{ + uint8_t hdr = 0; + if (!src_read_eq(src, &hdr, 1)) { + return RNP_ERROR_READ; + } + + bool last = false; + size_t partlen = 0; + if (!stream_read_partial_chunk_len(src, &partlen, &last)) { + return RNP_ERROR_BAD_FORMAT; + } + + uint8_t *buf = (uint8_t *) malloc(PGP_INPUT_CACHE_SIZE); + if (!buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + while (partlen > 0) { + size_t read = std::min(partlen, (size_t) PGP_INPUT_CACHE_SIZE); + if (!src_read_eq(src, buf, read)) { + free(buf); + return RNP_ERROR_READ; + } + if (dst) { + dst_write(dst, buf, read); + } + partlen -= read; + if (partlen > 0) { + continue; + } + if (last) { + break; + } + if (!stream_read_partial_chunk_len(src, &partlen, &last)) { + free(buf); + return RNP_ERROR_BAD_FORMAT; + } + } + free(buf); + return RNP_SUCCESS; +} + +rnp_result_t +stream_read_packet(pgp_source_t *src, pgp_dest_t *dst) +{ + if (stream_old_indeterminate_pkt_len(src)) { + return dst_write_src(src, dst, PGP_MAX_OLD_LEN_INDETERMINATE_PKT_SIZE); + } + + if (stream_partial_pkt_len(src)) { + return stream_read_packet_partial(src, dst); + } + + try { + pgp_packet_body_t body(PGP_PKT_RESERVED); + rnp_result_t ret = body.read(*src); + if (dst) { + body.write(*dst, false); + } + return ret; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +rnp_result_t +stream_skip_packet(pgp_source_t *src) +{ + return stream_read_packet(src, NULL); +} + +rnp_result_t +stream_parse_marker(pgp_source_t &src) +{ + try { + pgp_packet_body_t pkt(PGP_PKT_MARKER); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + if ((pkt.size() != PGP_MARKER_LEN) || + memcmp(pkt.data(), PGP_MARKER_CONTENTS, PGP_MARKER_LEN)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } +} + +bool +is_key_pkt(int tag) +{ + switch (tag) { + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_PUBLIC_SUBKEY: + case PGP_PKT_SECRET_KEY: + case PGP_PKT_SECRET_SUBKEY: + return true; + default: + return false; + } +} + +bool +is_subkey_pkt(int tag) +{ + return (tag == PGP_PKT_PUBLIC_SUBKEY) || (tag == PGP_PKT_SECRET_SUBKEY); +} + +bool +is_primary_key_pkt(int tag) +{ + return (tag == PGP_PKT_PUBLIC_KEY) || (tag == PGP_PKT_SECRET_KEY); +} + +bool +is_public_key_pkt(int tag) +{ + switch (tag) { + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_PUBLIC_SUBKEY: + return true; + default: + return false; + } +} + +bool +is_secret_key_pkt(int tag) +{ + switch (tag) { + case PGP_PKT_SECRET_KEY: + case PGP_PKT_SECRET_SUBKEY: + return true; + default: + return false; + } +} + +bool +is_rsa_key_alg(pgp_pubkey_alg_t alg) +{ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return true; + default: + return false; + } +} + +pgp_packet_body_t::pgp_packet_body_t(pgp_pkt_type_t tag) +{ + data_.reserve(16); + tag_ = tag; + secure_ = is_secret_key_pkt(tag); +} + +pgp_packet_body_t::pgp_packet_body_t(const uint8_t *data, size_t len) +{ + data_.assign(data, data + len); + tag_ = PGP_PKT_RESERVED; + secure_ = false; +} + +pgp_packet_body_t::~pgp_packet_body_t() +{ + if (secure_) { + secure_clear(data_.data(), data_.size()); + } +} + +uint8_t * +pgp_packet_body_t::data() noexcept +{ + return data_.data(); +} + +size_t +pgp_packet_body_t::size() const noexcept +{ + return data_.size(); +} + +size_t +pgp_packet_body_t::left() const noexcept +{ + return data_.size() - pos_; +} + +bool +pgp_packet_body_t::get(uint8_t &val) noexcept +{ + if (pos_ >= data_.size()) { + return false; + } + val = data_[pos_++]; + return true; +} + +bool +pgp_packet_body_t::get(uint16_t &val) noexcept +{ + if (pos_ + 2 > data_.size()) { + return false; + } + val = read_uint16(data_.data() + pos_); + pos_ += 2; + return true; +} + +bool +pgp_packet_body_t::get(uint32_t &val) noexcept +{ + if (pos_ + 4 > data_.size()) { + return false; + } + val = read_uint32(data_.data() + pos_); + pos_ += 4; + return true; +} + +bool +pgp_packet_body_t::get(uint8_t *val, size_t len) noexcept +{ + if (pos_ + len > data_.size()) { + return false; + } + memcpy(val, data_.data() + pos_, len); + pos_ += len; + return true; +} + +bool +pgp_packet_body_t::get(pgp_key_id_t &val) noexcept +{ + static_assert(std::tuple_size<pgp_key_id_t>::value == PGP_KEY_ID_SIZE, + "pgp_key_id_t size mismatch"); + return get(val.data(), val.size()); +} + +bool +pgp_packet_body_t::get(pgp_mpi_t &val) noexcept +{ + uint16_t bits = 0; + if (!get(bits)) { + return false; + } + size_t len = (bits + 7) >> 3; + if (len > PGP_MPINT_SIZE) { + RNP_LOG("too large mpi"); + return false; + } + if (!len) { + RNP_LOG("0 mpi"); + return false; + } + if (!get(val.mpi, len)) { + RNP_LOG("failed to read mpi body"); + return false; + } + /* check the mpi bit count */ + val.len = len; + size_t mbits = mpi_bits(&val); + if (mbits != bits) { + RNP_LOG( + "Warning! Wrong mpi bit count: got %" PRIu16 ", but actual is %zu", bits, mbits); + } + return true; +} + +bool +pgp_packet_body_t::get(pgp_curve_t &val) noexcept +{ + uint8_t oidlen = 0; + if (!get(oidlen)) { + return false; + } + uint8_t oid[MAX_CURVE_OID_HEX_LEN] = {0}; + if (!oidlen || (oidlen == 0xff) || (oidlen > sizeof(oid))) { + RNP_LOG("unsupported curve oid len: %" PRIu8, oidlen); + return false; + } + if (!get(oid, oidlen)) { + return false; + } + pgp_curve_t res = find_curve_by_OID(oid, oidlen); + if (res == PGP_CURVE_MAX) { + RNP_LOG("unsupported curve"); + return false; + } + val = res; + return true; +} + +bool +pgp_packet_body_t::get(pgp_s2k_t &s2k) noexcept +{ + uint8_t spec = 0, halg = 0; + if (!get(spec) || !get(halg)) { + return false; + } + s2k.specifier = (pgp_s2k_specifier_t) spec; + s2k.hash_alg = (pgp_hash_alg_t) halg; + + switch (s2k.specifier) { + case PGP_S2KS_SIMPLE: + return true; + case PGP_S2KS_SALTED: + return get(s2k.salt, PGP_SALT_SIZE); + case PGP_S2KS_ITERATED_AND_SALTED: { + uint8_t iter = 0; + if (!get(s2k.salt, PGP_SALT_SIZE) || !get(iter)) { + return false; + } + s2k.iterations = iter; + return true; + } + case PGP_S2KS_EXPERIMENTAL: { + try { + s2k.experimental = {data_.begin() + pos_, data_.end()}; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + uint8_t gnu[3] = {0}; + if (!get(gnu, 3) || memcmp(gnu, "GNU", 3)) { + RNP_LOG("Unknown experimental s2k. Skipping."); + pos_ = data_.size(); + s2k.gpg_ext_num = PGP_S2K_GPG_NONE; + return true; + } + uint8_t ext_num = 0; + if (!get(ext_num)) { + return false; + } + if ((ext_num != PGP_S2K_GPG_NO_SECRET) && (ext_num != PGP_S2K_GPG_SMARTCARD)) { + RNP_LOG("Unsupported gpg extension num: %" PRIu8 ", skipping", ext_num); + pos_ = data_.size(); + s2k.gpg_ext_num = PGP_S2K_GPG_NONE; + return true; + } + s2k.gpg_ext_num = (pgp_s2k_gpg_extension_t) ext_num; + if (s2k.gpg_ext_num == PGP_S2K_GPG_NO_SECRET) { + return true; + } + if (!get(s2k.gpg_serial_len)) { + RNP_LOG("Failed to get GPG serial len"); + return false; + } + size_t len = s2k.gpg_serial_len; + if (s2k.gpg_serial_len > 16) { + RNP_LOG("Warning: gpg_serial_len is %d", (int) len); + len = 16; + } + if (!get(s2k.gpg_serial, len)) { + RNP_LOG("Failed to get GPG serial"); + return false; + } + return true; + } + default: + RNP_LOG("unknown s2k specifier: %d", (int) s2k.specifier); + return false; + } +} + +void +pgp_packet_body_t::add(const void *data, size_t len) +{ + data_.insert(data_.end(), (uint8_t *) data, (uint8_t *) data + len); +} + +void +pgp_packet_body_t::add_byte(uint8_t bt) +{ + data_.push_back(bt); +} + +void +pgp_packet_body_t::add_uint16(uint16_t val) +{ + uint8_t bytes[2]; + write_uint16(bytes, val); + add(bytes, 2); +} + +void +pgp_packet_body_t::add_uint32(uint32_t val) +{ + uint8_t bytes[4]; + STORE32BE(bytes, val); + add(bytes, 4); +} + +void +pgp_packet_body_t::add(const pgp_key_id_t &val) +{ + add(val.data(), val.size()); +} + +void +pgp_packet_body_t::add(const pgp_mpi_t &val) +{ + if (!val.len) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + unsigned idx = 0; + while ((idx < val.len - 1) && (!val.mpi[idx])) { + idx++; + } + + unsigned bits = (val.len - idx - 1) << 3; + unsigned hibyte = val.mpi[idx]; + while (hibyte) { + bits++; + hibyte = hibyte >> 1; + } + + uint8_t hdr[2] = {(uint8_t)(bits >> 8), (uint8_t)(bits & 0xff)}; + add(hdr, 2); + add(val.mpi + idx, val.len - idx); +} + +void +pgp_packet_body_t::add_subpackets(const pgp_signature_t &sig, bool hashed) +{ + pgp_packet_body_t spbody(PGP_PKT_RESERVED); + + for (auto &subpkt : sig.subpkts) { + if (subpkt.hashed != hashed) { + continue; + } + + uint8_t splen[6]; + size_t lenlen = write_packet_len(splen, subpkt.len + 1); + spbody.add(splen, lenlen); + spbody.add_byte(subpkt.type | (subpkt.critical << 7)); + spbody.add(subpkt.data, subpkt.len); + } + + if (spbody.data_.size() > 0xffff) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + add_uint16(spbody.data_.size()); + add(spbody.data_.data(), spbody.data_.size()); +} + +void +pgp_packet_body_t::add(const pgp_curve_t curve) +{ + const ec_curve_desc_t *desc = get_curve_desc(curve); + if (!desc) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + add_byte((uint8_t) desc->OIDhex_len); + add(desc->OIDhex, (uint8_t) desc->OIDhex_len); +} + +void +pgp_packet_body_t::add(const pgp_s2k_t &s2k) +{ + add_byte(s2k.specifier); + add_byte(s2k.hash_alg); + + switch (s2k.specifier) { + case PGP_S2KS_SIMPLE: + return; + case PGP_S2KS_SALTED: + add(s2k.salt, PGP_SALT_SIZE); + return; + case PGP_S2KS_ITERATED_AND_SALTED: { + unsigned iter = s2k.iterations; + if (iter > 255) { + iter = pgp_s2k_encode_iterations(iter); + } + add(s2k.salt, PGP_SALT_SIZE); + add_byte(iter); + return; + } + case PGP_S2KS_EXPERIMENTAL: { + if ((s2k.gpg_ext_num != PGP_S2K_GPG_NO_SECRET) && + (s2k.gpg_ext_num != PGP_S2K_GPG_SMARTCARD)) { + RNP_LOG("Unknown experimental s2k."); + add(s2k.experimental.data(), s2k.experimental.size()); + return; + } + add("GNU", 3); + add_byte(s2k.gpg_ext_num); + if (s2k.gpg_ext_num == PGP_S2K_GPG_SMARTCARD) { + static_assert(sizeof(s2k.gpg_serial) == 16, "invalid gpg serial length"); + size_t slen = s2k.gpg_serial_len > 16 ? 16 : s2k.gpg_serial_len; + add_byte(s2k.gpg_serial_len); + add(s2k.gpg_serial, slen); + } + return; + } + default: + RNP_LOG("unknown s2k specifier"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +rnp_result_t +pgp_packet_body_t::read(pgp_source_t &src) noexcept +{ + /* Make sure we have enough data for packet header */ + if (!src_peek_eq(&src, hdr_, 2)) { + return RNP_ERROR_READ; + } + + /* Read the packet header and length */ + size_t len = 0; + if (!stream_pkt_hdr_len(src, len)) { + return RNP_ERROR_BAD_FORMAT; + } + if (!src_peek_eq(&src, hdr_, len)) { + return RNP_ERROR_READ; + } + hdr_len_ = len; + + int ptag = get_packet_type(hdr_[0]); + if ((ptag < 0) || ((tag_ != PGP_PKT_RESERVED) && (tag_ != ptag))) { + RNP_LOG("tag mismatch: %d vs %d", (int) tag_, ptag); + return RNP_ERROR_BAD_FORMAT; + } + tag_ = (pgp_pkt_type_t) ptag; + + if (!stream_read_pkt_len(&src, &len)) { + return RNP_ERROR_READ; + } + + /* early exit for the empty packet */ + if (!len) { + return RNP_SUCCESS; + } + + if (len > PGP_MAX_PKT_SIZE) { + RNP_LOG("too large packet"); + return RNP_ERROR_BAD_FORMAT; + } + + /* Read the packet contents */ + try { + data_.resize(len); + } catch (const std::exception &e) { + RNP_LOG("malloc of %d bytes failed, %s", (int) len, e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + size_t read = 0; + if (!src_read(&src, data_.data(), len, &read) || (read != len)) { + RNP_LOG("read %d instead of %d", (int) read, (int) len); + return RNP_ERROR_READ; + } + pos_ = 0; + return RNP_SUCCESS; +} + +void +pgp_packet_body_t::write(pgp_dest_t &dst, bool hdr) noexcept +{ + if (hdr) { + uint8_t hdrbt[6] = { + (uint8_t)(tag_ | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT), 0, 0, 0, 0, 0}; + size_t hlen = 1 + write_packet_len(&hdrbt[1], data_.size()); + dst_write(&dst, hdrbt, hlen); + } + dst_write(&dst, data_.data(), data_.size()); +} + +void +pgp_packet_body_t::mark_secure(bool secure) noexcept +{ + secure_ = secure; +} + +void +pgp_sk_sesskey_t::write(pgp_dest_t &dst) const +{ + pgp_packet_body_t pktbody(PGP_PKT_SK_SESSION_KEY); + /* version and algorithm fields */ + pktbody.add_byte(version); + pktbody.add_byte(alg); + if (version == PGP_SKSK_V5) { + pktbody.add_byte(aalg); + } + /* S2K specifier */ + pktbody.add_byte(s2k.specifier); + pktbody.add_byte(s2k.hash_alg); + + switch (s2k.specifier) { + case PGP_S2KS_SIMPLE: + break; + case PGP_S2KS_SALTED: + pktbody.add(s2k.salt, sizeof(s2k.salt)); + break; + case PGP_S2KS_ITERATED_AND_SALTED: + pktbody.add(s2k.salt, sizeof(s2k.salt)); + pktbody.add_byte(s2k.iterations); + break; + default: + RNP_LOG("Unexpected s2k specifier: %d", (int) s2k.specifier); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + /* v5 : iv */ + if (version == PGP_SKSK_V5) { + pktbody.add(iv, ivlen); + } + /* encrypted key and auth tag for v5 */ + if (enckeylen) { + pktbody.add(enckey, enckeylen); + } + /* write packet */ + pktbody.write(dst); +} + +rnp_result_t +pgp_sk_sesskey_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_SK_SESSION_KEY); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + + /* version */ + uint8_t bt; + if (!pkt.get(bt) || ((bt != PGP_SKSK_V4) && (bt != PGP_SKSK_V5))) { + RNP_LOG("wrong packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = bt; + /* symmetric algorithm */ + if (!pkt.get(bt)) { + RNP_LOG("failed to get symm alg"); + return RNP_ERROR_BAD_FORMAT; + } + alg = (pgp_symm_alg_t) bt; + + if (version == PGP_SKSK_V5) { + /* aead algorithm */ + if (!pkt.get(bt)) { + RNP_LOG("failed to get aead alg"); + return RNP_ERROR_BAD_FORMAT; + } + aalg = (pgp_aead_alg_t) bt; + if ((aalg != PGP_AEAD_EAX) && (aalg != PGP_AEAD_OCB)) { + RNP_LOG("unsupported AEAD algorithm : %d", (int) aalg); + return RNP_ERROR_BAD_PARAMETERS; + } + } + + /* s2k */ + if (!pkt.get(s2k)) { + RNP_LOG("failed to parse s2k"); + return RNP_ERROR_BAD_FORMAT; + } + + /* v4 key */ + if (version == PGP_SKSK_V4) { + /* encrypted session key if present */ + size_t keylen = pkt.left(); + if (keylen) { + if (keylen > PGP_MAX_KEY_SIZE + 1) { + RNP_LOG("too long esk"); + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(enckey, keylen)) { + RNP_LOG("failed to get key"); + return RNP_ERROR_BAD_FORMAT; + } + } + enckeylen = keylen; + return RNP_SUCCESS; + } + + /* v5: iv + esk + tag. For both EAX and OCB ivlen and taglen are 16 octets */ + size_t noncelen = pgp_cipher_aead_nonce_len(aalg); + size_t taglen = pgp_cipher_aead_tag_len(aalg); + size_t keylen = 0; + + if (pkt.left() > noncelen + taglen + PGP_MAX_KEY_SIZE) { + RNP_LOG("too long esk"); + return RNP_ERROR_BAD_FORMAT; + } + if (pkt.left() < noncelen + taglen + 8) { + RNP_LOG("too short esk"); + return RNP_ERROR_BAD_FORMAT; + } + /* iv */ + if (!pkt.get(iv, noncelen)) { + RNP_LOG("failed to get iv"); + return RNP_ERROR_BAD_FORMAT; + } + ivlen = noncelen; + + /* key */ + keylen = pkt.left(); + if (!pkt.get(enckey, keylen)) { + RNP_LOG("failed to get key"); + return RNP_ERROR_BAD_FORMAT; + } + enckeylen = keylen; + return RNP_SUCCESS; +} + +void +pgp_pk_sesskey_t::write(pgp_dest_t &dst) const +{ + pgp_packet_body_t pktbody(PGP_PKT_PK_SESSION_KEY); + pktbody.add_byte(version); + pktbody.add(key_id); + pktbody.add_byte(alg); + pktbody.add(material_buf.data(), material_buf.size()); + pktbody.write(dst); +} + +rnp_result_t +pgp_pk_sesskey_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_PK_SESSION_KEY); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + /* version */ + uint8_t bt = 0; + if (!pkt.get(bt) || (bt != PGP_PKSK_V3)) { + RNP_LOG("wrong packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = bt; + /* key id */ + if (!pkt.get(key_id)) { + RNP_LOG("failed to get key id"); + return RNP_ERROR_BAD_FORMAT; + } + /* public key algorithm */ + if (!pkt.get(bt)) { + RNP_LOG("failed to get palg"); + return RNP_ERROR_BAD_FORMAT; + } + alg = (pgp_pubkey_alg_t) bt; + + /* raw signature material */ + if (!pkt.left()) { + RNP_LOG("No encrypted material"); + return RNP_ERROR_BAD_FORMAT; + } + try { + material_buf.resize(pkt.left()); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + /* we cannot fail here */ + pkt.get(material_buf.data(), material_buf.size()); + /* check whether it can be parsed */ + pgp_encrypted_material_t material = {}; + if (!parse_material(material)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +bool +pgp_pk_sesskey_t::parse_material(pgp_encrypted_material_t &material) const +{ + pgp_packet_body_t pkt(material_buf.data(), material_buf.size()); + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + /* RSA m */ + if (!pkt.get(material.rsa.m)) { + RNP_LOG("failed to get rsa m"); + return false; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + /* ElGamal g, m */ + if (!pkt.get(material.eg.g) || !pkt.get(material.eg.m)) { + RNP_LOG("failed to get elgamal mpis"); + return false; + } + break; + case PGP_PKA_SM2: + /* SM2 m */ + if (!pkt.get(material.sm2.m)) { + RNP_LOG("failed to get sm2 m"); + return false; + } + break; + case PGP_PKA_ECDH: { + /* ECDH ephemeral point */ + if (!pkt.get(material.ecdh.p)) { + RNP_LOG("failed to get ecdh p"); + return false; + } + /* ECDH m */ + uint8_t bt = 0; + if (!pkt.get(bt)) { + RNP_LOG("failed to get ecdh m len"); + return false; + } + if (bt > ECDH_WRAPPED_KEY_SIZE) { + RNP_LOG("wrong ecdh m len"); + return false; + } + material.ecdh.mlen = bt; + if (!pkt.get(material.ecdh.m, bt)) { + RNP_LOG("failed to get ecdh m len"); + return false; + } + break; + } + default: + RNP_LOG("unknown pk alg %d", (int) alg); + return false; + } + + if (pkt.left()) { + RNP_LOG("extra %d bytes in pk packet", (int) pkt.left()); + return false; + } + return true; +} + +void +pgp_pk_sesskey_t::write_material(const pgp_encrypted_material_t &material) +{ + pgp_packet_body_t pktbody(PGP_PKT_PK_SESSION_KEY); + + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + pktbody.add(material.rsa.m); + break; + case PGP_PKA_SM2: + pktbody.add(material.sm2.m); + break; + case PGP_PKA_ECDH: + pktbody.add(material.ecdh.p); + pktbody.add_byte(material.ecdh.mlen); + pktbody.add(material.ecdh.m, material.ecdh.mlen); + break; + case PGP_PKA_ELGAMAL: + pktbody.add(material.eg.g); + pktbody.add(material.eg.m); + break; + default: + RNP_LOG("Unknown pk alg: %d", (int) alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + material_buf = {pktbody.data(), pktbody.data() + pktbody.size()}; +} + +void +pgp_one_pass_sig_t::write(pgp_dest_t &dst) const +{ + pgp_packet_body_t pktbody(PGP_PKT_ONE_PASS_SIG); + pktbody.add_byte(version); + pktbody.add_byte(type); + pktbody.add_byte(halg); + pktbody.add_byte(palg); + pktbody.add(keyid); + pktbody.add_byte(nested); + pktbody.write(dst); +} + +rnp_result_t +pgp_one_pass_sig_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_ONE_PASS_SIG); + /* Read the packet into memory */ + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + + uint8_t buf[13] = {0}; + if ((pkt.size() != 13) || !pkt.get(buf, 13)) { + return RNP_ERROR_BAD_FORMAT; + } + /* version */ + if (buf[0] != 3) { + RNP_LOG("wrong packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = buf[0]; + /* signature type */ + type = (pgp_sig_type_t) buf[1]; + /* hash algorithm */ + halg = (pgp_hash_alg_t) buf[2]; + /* pk algorithm */ + palg = (pgp_pubkey_alg_t) buf[3]; + /* key id */ + static_assert(std::tuple_size<decltype(keyid)>::value == PGP_KEY_ID_SIZE, + "pgp_one_pass_sig_t.keyid size mismatch"); + memcpy(keyid.data(), &buf[4], PGP_KEY_ID_SIZE); + /* nested flag */ + nested = buf[12]; + return RNP_SUCCESS; +} diff --git a/src/librepgp/stream-packet.h b/src/librepgp/stream-packet.h new file mode 100644 index 0000000..f88c96f --- /dev/null +++ b/src/librepgp/stream-packet.h @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_PACKET_H_ +#define STREAM_PACKET_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "types.h" +#include "stream-common.h" + +/* maximum size of the 'small' packet */ +#define PGP_MAX_PKT_SIZE 0x100000 + +/* maximum size of indeterminate-size packet allowed with old length format */ +#define PGP_MAX_OLD_LEN_INDETERMINATE_PKT_SIZE 0x40000000 + +typedef struct pgp_packet_hdr_t { + pgp_pkt_type_t tag; /* packet tag */ + uint8_t hdr[PGP_MAX_HEADER_SIZE]; /* PGP packet header, needed for AEAD */ + size_t hdr_len; /* length of the header */ + size_t pkt_len; /* packet body length if non-partial and non-indeterminate */ + bool partial; /* partial length packet */ + bool indeterminate; /* indeterminate length packet */ +} pgp_packet_hdr_t; + +/* structure for convenient writing or parsing of non-stream packets */ +typedef struct pgp_packet_body_t { + private: + pgp_pkt_type_t tag_; /* packet tag */ + std::vector<uint8_t> data_; /* packet bytes */ + /* fields below are filled only for parsed packet */ + uint8_t hdr_[PGP_MAX_HEADER_SIZE]{}; /* packet header bytes */ + size_t hdr_len_{}; /* number of bytes in hdr */ + size_t pos_{}; /* current read position in packet data */ + bool secure_{}; /* contents of the packet are secure so must be wiped in the destructor */ + public: + /** @brief initialize writing of packet body + * @param tag tag of the packet + **/ + pgp_packet_body_t(pgp_pkt_type_t tag); + /** @brief init packet body (without headers) with memory. Used for easier data parsing. + * @param data buffer with packet body part + * @param len number of available bytes in mem + */ + pgp_packet_body_t(const uint8_t *data, size_t len); + + pgp_packet_body_t(const pgp_packet_body_t &src) = delete; + pgp_packet_body_t(pgp_packet_body_t &&src) = delete; + pgp_packet_body_t &operator=(const pgp_packet_body_t &) = delete; + pgp_packet_body_t &operator=(pgp_packet_body_t &&) = delete; + ~pgp_packet_body_t(); + + /** @brief pointer to the data, kept in the packet */ + uint8_t *data() noexcept; + /** @brief number of bytes, kept in the packet (without the header) */ + size_t size() const noexcept; + /** @brief number of bytes left to read */ + size_t left() const noexcept; + /** @brief get next byte from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint8_t &val) noexcept; + /** @brief get next big-endian uint16 from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint16_t &val) noexcept; + /** @brief get next big-endian uint32 from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint32_t &val) noexcept; + /** @brief get some bytes from the packet body, populated with read() call. + * @param val packet body bytes will be stored here. Must be capable of storing len bytes. + * @param len number of bytes to read + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint8_t *val, size_t len) noexcept; + /** @brief get next keyid from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(pgp_key_id_t &val) noexcept; + /** @brief get next mpi from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached + * or mpi is ill-formed) + **/ + bool get(pgp_mpi_t &val) noexcept; + /** @brief Read ECC key curve and convert it to pgp_curve_t */ + bool get(pgp_curve_t &val) noexcept; + /** @brief read s2k from the packet */ + bool get(pgp_s2k_t &s2k) noexcept; + /** @brief append some bytes to the packet body */ + void add(const void *data, size_t len); + /** @brief append single byte to the packet body */ + void add_byte(uint8_t bt); + /** @brief append big endian 16-bit value to the packet body */ + void add_uint16(uint16_t val); + /** @brief append big endian 32-bit value to the packet body */ + void add_uint32(uint32_t val); + /** @brief append keyid to the packet body */ + void add(const pgp_key_id_t &val); + /** @brief add pgp mpi (including header) to the packet body */ + void add(const pgp_mpi_t &val); + /** + * @brief add pgp signature subpackets (including their length) to the packet body + * @param sig signature, containing subpackets + * @param hashed whether write hashed or not hashed subpackets + */ + void add_subpackets(const pgp_signature_t &sig, bool hashed); + /** @brief add ec curve description to the packet body */ + void add(const pgp_curve_t curve); + /** @brief add s2k description to the packet body */ + void add(const pgp_s2k_t &s2k); + /** @brief read 'short-length' packet body (including tag and length bytes) from the source + * @param src source to read from + * @return RNP_SUCCESS or error code if operation failed + **/ + rnp_result_t read(pgp_source_t &src) noexcept; + /** @brief write packet header, length and body to the dst + * @param dst destination to write to. + * @param hdr write packet's header or not + **/ + void write(pgp_dest_t &dst, bool hdr = true) noexcept; + /** @brief mark contents as secure, so secure_clear() must be called in the destructor */ + void mark_secure(bool secure = true) noexcept; +} pgp_packet_body_t; + +/** public-key encrypted session key packet */ +typedef struct pgp_pk_sesskey_t { + unsigned version{}; + pgp_key_id_t key_id{}; + pgp_pubkey_alg_t alg{}; + std::vector<uint8_t> material_buf{}; + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); + /** + * @brief Parse encrypted material which is stored in packet in raw. + * @param material on success parsed material will be stored here. + * @return true on success or false otherwise. May also throw an exception. + */ + bool parse_material(pgp_encrypted_material_t &material) const; + /** + * @brief Write encrypted material to the material_buf. + * @param material populated encrypted material. + */ + void write_material(const pgp_encrypted_material_t &material); +} pgp_pk_sesskey_t; + +/** pkp_sk_sesskey_t */ +typedef struct pgp_sk_sesskey_t { + unsigned version{}; + pgp_symm_alg_t alg{}; + pgp_s2k_t s2k{}; + uint8_t enckey[PGP_MAX_KEY_SIZE + PGP_AEAD_MAX_TAG_LEN + 1]{}; + unsigned enckeylen{}; + /* v5 specific fields */ + pgp_aead_alg_t aalg{}; + uint8_t iv[PGP_MAX_BLOCK_SIZE]{}; + unsigned ivlen{}; + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); +} pgp_sk_sesskey_t; + +/** pgp_one_pass_sig_t */ +typedef struct pgp_one_pass_sig_t { + uint8_t version{}; + pgp_sig_type_t type{}; + pgp_hash_alg_t halg{}; + pgp_pubkey_alg_t palg{}; + pgp_key_id_t keyid{}; + unsigned nested{}; + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); +} pgp_one_pass_sig_t; + +/** Struct to hold userid or userattr packet. We don't parse userattr now, just storing the + * binary blob as it is. It may be distinguished by tag field. + */ +typedef struct pgp_userid_pkt_t { + pgp_pkt_type_t tag; + uint8_t * uid; + size_t uid_len; + + pgp_userid_pkt_t() : tag(PGP_PKT_RESERVED), uid(NULL), uid_len(0){}; + pgp_userid_pkt_t(const pgp_userid_pkt_t &src); + pgp_userid_pkt_t(pgp_userid_pkt_t &&src); + pgp_userid_pkt_t &operator=(pgp_userid_pkt_t &&src); + pgp_userid_pkt_t &operator=(const pgp_userid_pkt_t &src); + bool operator==(const pgp_userid_pkt_t &src) const; + bool operator!=(const pgp_userid_pkt_t &src) const; + ~pgp_userid_pkt_t(); + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); +} pgp_userid_pkt_t; + +uint16_t read_uint16(const uint8_t *buf); + +uint32_t read_uint32(const uint8_t *buf); + +void write_uint16(uint8_t *buf, uint16_t val); + +/** @brief write new packet length + * @param buf pre-allocated buffer, must have 5 bytes + * @param len packet length + * @return number of bytes, saved in buf + **/ +size_t write_packet_len(uint8_t *buf, size_t len); + +/** @brief get packet type from the packet header byte + * @param ptag first byte of the packet header + * @return packet type or -1 if ptag is wrong + **/ +int get_packet_type(uint8_t ptag); + +/** @brief peek the packet type from the stream + * @param src source to peek from + * @return packet tag or -1 if read failed or packet header is malformed + */ +int stream_pkt_type(pgp_source_t &src); + +/** @brief Peek length of the packet header. Returns false on error. + * @param src source to read length from + * @param hdrlen header length will be put here on success. Cannot be NULL. + * @return true on success or false if there is a read error or packet length + * is ill-formed + **/ +bool stream_pkt_hdr_len(pgp_source_t &src, size_t &hdrlen); + +bool stream_old_indeterminate_pkt_len(pgp_source_t *src); + +bool stream_partial_pkt_len(pgp_source_t *src); + +size_t get_partial_pkt_len(uint8_t blen); + +/** @brief Read packet length for fixed-size (say, small) packet. Returns false on error. + * Will also read packet tag byte. We do not allow partial length here as well as large + * packets (so ignoring possible size_t overflow) + * + * @param src source to read length from + * @param pktlen packet length will be stored here on success. Cannot be NULL. + * @return true on success or false if there is read error or packet length is ill-formed + **/ +bool stream_read_pkt_len(pgp_source_t *src, size_t *pktlen); + +/** @brief Read partial packet chunk length. + * + * @param src source to read length from + * @param clen chunk length will be stored here on success. Cannot be NULL. + * @param last will be set to true if chunk is last (i.e. has non-partial length) + * @return true on success or false if there is read error or packet length is ill-formed + **/ +bool stream_read_partial_chunk_len(pgp_source_t *src, size_t *clen, bool *last); + +/** @brief get and parse OpenPGP packet header to the structure. + * Note: this will not read but just peek required bytes. + * + * @param src source to read from + * @param hdr header structure + * @return RNP_SUCCESS or error code if operation failed + **/ +rnp_result_t stream_peek_packet_hdr(pgp_source_t *src, pgp_packet_hdr_t *hdr); + +/* Packet handling functions */ + +/** @brief read OpenPGP packet from the stream, and write its contents to another stream. + * @param src source with packet data + * @param dst destination to write packet contents. All write failures on dst + * will be ignored. Can be NULL if you need just to skip packet. + * @return RNP_SUCCESS or error code if operation failed. + */ +rnp_result_t stream_read_packet(pgp_source_t *src, pgp_dest_t *dst); + +rnp_result_t stream_skip_packet(pgp_source_t *src); + +rnp_result_t stream_parse_marker(pgp_source_t &src); + +/* Public/Private key or Subkey */ + +bool is_key_pkt(int tag); + +bool is_subkey_pkt(int tag); + +bool is_primary_key_pkt(int tag); + +bool is_public_key_pkt(int tag); + +bool is_secret_key_pkt(int tag); + +bool is_rsa_key_alg(pgp_pubkey_alg_t alg); + +#endif diff --git a/src/librepgp/stream-parse.cpp b/src/librepgp/stream-parse.cpp new file mode 100644 index 0000000..5ec4d64 --- /dev/null +++ b/src/librepgp/stream-parse.cpp @@ -0,0 +1,2636 @@ +/* + * Copyright (c) 2017-2023, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <string> +#include <vector> +#include <time.h> +#include <cinttypes> +#include <cassert> +#include <rnp/rnp_def.h> +#include "stream-ctx.h" +#include "stream-def.h" +#include "stream-parse.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "stream-sig.h" +#include "str-utils.h" +#include "types.h" +#include "crypto/s2k.h" +#include "crypto.h" +#include "crypto/signatures.h" +#include "fingerprint.h" +#include "pgp-key.h" + +#ifdef HAVE_ZLIB_H +#include <zlib.h> +#endif +#ifdef HAVE_BZLIB_H +#include <bzlib.h> +#endif + +typedef enum pgp_message_t { + PGP_MESSAGE_UNKNOWN = 0, + PGP_MESSAGE_NORMAL, + PGP_MESSAGE_DETACHED, + PGP_MESSAGE_CLEARTEXT +} pgp_message_t; + +typedef struct pgp_processing_ctx_t { + pgp_parse_handler_t handler; + pgp_source_t * signed_src; + pgp_source_t * literal_src; + pgp_message_t msg_type; + pgp_dest_t output; + std::list<pgp_source_t> sources; + + ~pgp_processing_ctx_t(); +} pgp_processing_ctx_t; + +/* common fields for encrypted, compressed and literal data */ +typedef struct pgp_source_packet_param_t { + pgp_source_t * readsrc; /* source to read from, could be partial*/ + pgp_source_t * origsrc; /* original source passed to init_*_src */ + pgp_packet_hdr_t hdr; /* packet header info */ +} pgp_source_packet_param_t; + +typedef struct pgp_source_encrypted_param_t { + pgp_source_packet_param_t pkt{}; /* underlying packet-related params */ + std::vector<pgp_sk_sesskey_t> symencs; /* array of sym-encrypted session keys */ + std::vector<pgp_pk_sesskey_t> pubencs; /* array of pk-encrypted session keys */ + rnp::AuthType auth_type; /* Authentication type */ + bool auth_validated{}; /* Auth tag (MDC or AEAD) was already validated */ + pgp_crypt_t decrypt{}; /* decrypting crypto */ + std::unique_ptr<rnp::Hash> mdc; /* mdc SHA1 hash */ + size_t chunklen{}; /* size of AEAD chunk in bytes */ + size_t chunkin{}; /* number of bytes read from the current chunk */ + size_t chunkidx{}; /* index of the current chunk */ + uint8_t cache[PGP_AEAD_CACHE_LEN]; /* read cache */ + size_t cachelen{}; /* number of bytes in the cache */ + size_t cachepos{}; /* index of first unread byte in the cache */ + pgp_aead_hdr_t aead_hdr; /* AEAD encryption parameters */ + uint8_t aead_ad[PGP_AEAD_MAX_AD_LEN]; /* additional data */ + size_t aead_adlen{}; /* length of the additional data */ + pgp_symm_alg_t salg; /* data encryption algorithm */ + pgp_parse_handler_t * handler{}; /* parsing handler with callbacks */ + + pgp_source_encrypted_param_t() : auth_type(rnp::AuthType::None), salg(PGP_SA_UNKNOWN) + { + } + + bool + use_cfb() + { + return auth_type != rnp::AuthType::AEADv1; + } +} pgp_source_encrypted_param_t; + +typedef struct pgp_source_signed_param_t { + pgp_parse_handler_t *handler; /* parsing handler with callbacks */ + pgp_source_t * readsrc; /* source to read from */ + bool detached; /* detached signature */ + bool cleartext; /* source is cleartext signed */ + bool clr_eod; /* cleartext data is over */ + bool clr_fline; /* first line of the cleartext */ + bool clr_mline; /* in the middle of the very long line */ + uint8_t out[CT_BUF_LEN]; /* cleartext output cache for easier parsing */ + size_t outlen; /* total bytes in out */ + size_t outpos; /* offset of first available byte in out */ + bool max_line_warn; /* warning about too long line is already issued */ + size_t text_line_len; /* length of a current line in a text document */ + long stripped_crs; /* number of trailing CR characters stripped from the end of the last + processed chunk */ + + std::vector<pgp_one_pass_sig_t> onepasses; /* list of one-pass singatures */ + std::list<pgp_signature_t> sigs; /* list of signatures */ + std::vector<pgp_signature_info_t> siginfos; /* signature validation info */ + rnp::HashList hashes; /* hash contexts */ + rnp::HashList txt_hashes; /* hash contexts for text-mode sigs */ + + pgp_source_signed_param_t() = default; + ~pgp_source_signed_param_t() = default; +} pgp_source_signed_param_t; + +typedef struct pgp_source_compressed_param_t { + pgp_source_packet_param_t pkt; /* underlying packet-related params */ + pgp_compression_type_t alg; + union { + z_stream z; + bz_stream bz; + }; + uint8_t in[PGP_INPUT_CACHE_SIZE / 2]; + size_t inpos; + size_t inlen; + bool zend; +} pgp_source_compressed_param_t; + +typedef struct pgp_source_literal_param_t { + pgp_source_packet_param_t pkt; /* underlying packet-related params */ + pgp_literal_hdr_t hdr; /* literal packet fields */ +} pgp_source_literal_param_t; + +typedef struct pgp_source_partial_param_t { + pgp_source_t *readsrc; /* source to read from */ + int type; /* type of the packet */ + size_t psize; /* size of the current part */ + size_t pleft; /* bytes left to read from the current part */ + bool last; /* current part is last */ +} pgp_source_partial_param_t; + +static bool +is_pgp_source(pgp_source_t &src) +{ + uint8_t buf; + if (!src_peek_eq(&src, &buf, 1)) { + return false; + } + + switch (get_packet_type(buf)) { + case PGP_PKT_PK_SESSION_KEY: + case PGP_PKT_SK_SESSION_KEY: + case PGP_PKT_ONE_PASS_SIG: + case PGP_PKT_SIGNATURE: + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_COMPRESSED: + case PGP_PKT_LITDATA: + case PGP_PKT_MARKER: + return true; + default: + return false; + } +} + +static bool +partial_pkt_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + if (src->eof) { + *readres = 0; + return true; + } + + pgp_source_partial_param_t *param = (pgp_source_partial_param_t *) src->param; + if (!param) { + return false; + } + + size_t read; + size_t write = 0; + while (len > 0) { + if (!param->pleft && param->last) { + // we have the last chunk + *readres = write; + return true; + } + if (!param->pleft) { + // reading next chunk + if (!stream_read_partial_chunk_len(param->readsrc, &read, ¶m->last)) { + return false; + } + param->psize = read; + param->pleft = read; + } + + if (!param->pleft) { + *readres = write; + return true; + } + + read = param->pleft > len ? len : param->pleft; + if (!src_read(param->readsrc, buf, read, &read)) { + RNP_LOG("failed to read data chunk"); + return false; + } + if (!read) { + RNP_LOG("unexpected eof"); + *readres = write; + return true; + } + write += read; + len -= read; + buf = (uint8_t *) buf + read; + param->pleft -= read; + } + + *readres = write; + return true; +} + +static void +partial_pkt_src_close(pgp_source_t *src) +{ + pgp_source_partial_param_t *param = (pgp_source_partial_param_t *) src->param; + if (param) { + free(src->param); + src->param = NULL; + } +} + +static rnp_result_t +init_partial_pkt_src(pgp_source_t *src, pgp_source_t *readsrc, pgp_packet_hdr_t &hdr) +{ + pgp_source_partial_param_t *param; + if (!init_src_common(src, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + assert(hdr.partial); + /* we are sure that header is indeterminate */ + param = (pgp_source_partial_param_t *) src->param; + param->type = hdr.tag; + param->psize = get_partial_pkt_len(hdr.hdr[1]); + param->pleft = param->psize; + param->last = false; + param->readsrc = readsrc; + + src->read = partial_pkt_src_read; + src->close = partial_pkt_src_close; + src->type = PGP_STREAM_PARLEN_PACKET; + + if (param->psize < PGP_PARTIAL_PKT_FIRST_PART_MIN_SIZE) { + RNP_LOG("first part of partial length packet sequence has size %d and that's less " + "than allowed by the protocol", + (int) param->psize); + } + + return RNP_SUCCESS; +} + +static bool +literal_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + pgp_source_literal_param_t *param = (pgp_source_literal_param_t *) src->param; + if (!param) { + return false; + } + return src_read(param->pkt.readsrc, buf, len, read); +} + +static void +literal_src_close(pgp_source_t *src) +{ + pgp_source_literal_param_t *param = (pgp_source_literal_param_t *) src->param; + if (param) { + if (param->pkt.hdr.partial) { + src_close(param->pkt.readsrc); + free(param->pkt.readsrc); + param->pkt.readsrc = NULL; + } + + free(src->param); + src->param = NULL; + } +} + +static bool +compressed_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_compressed_param_t *param = (pgp_source_compressed_param_t *) src->param; + if (!param) { + return false; + } + + if (src->eof || param->zend) { + *readres = 0; + return true; + } + + if (param->alg == PGP_C_NONE) { + if (!src_read(param->pkt.readsrc, buf, len, readres)) { + RNP_LOG("failed to read uncompressed data"); + return false; + } + return true; + } + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + param->z.next_out = (Bytef *) buf; + param->z.avail_out = len; + param->z.next_in = param->in + param->inpos; + param->z.avail_in = param->inlen - param->inpos; + + while ((param->z.avail_out > 0) && (!param->zend)) { + if (param->z.avail_in == 0) { + size_t read = 0; + if (!src_read(param->pkt.readsrc, param->in, sizeof(param->in), &read)) { + RNP_LOG("failed to read data"); + return false; + } + param->z.next_in = param->in; + param->z.avail_in = read; + param->inlen = read; + param->inpos = 0; + } + int ret = inflate(¶m->z, Z_SYNC_FLUSH); + if (ret == Z_STREAM_END) { + param->zend = true; + if (param->z.avail_in > 0) { + RNP_LOG("data beyond the end of z stream"); + } + break; + } + if (ret != Z_OK) { + RNP_LOG("inflate error %d", ret); + return false; + } + if (!param->z.avail_in && src_eof(param->pkt.readsrc)) { + RNP_LOG("unexpected end of zlib stream"); + return false; + } + } + param->inpos = param->z.next_in - param->in; + *readres = len - param->z.avail_out; + return true; + } +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + param->bz.next_out = (char *) buf; + param->bz.avail_out = len; + param->bz.next_in = (char *) (param->in + param->inpos); + param->bz.avail_in = param->inlen - param->inpos; + + while ((param->bz.avail_out > 0) && (!param->zend)) { + if (param->bz.avail_in == 0) { + size_t read = 0; + if (!src_read(param->pkt.readsrc, param->in, sizeof(param->in), &read)) { + RNP_LOG("failed to read data"); + return false; + } + param->bz.next_in = (char *) param->in; + param->bz.avail_in = read; + param->inlen = read; + param->inpos = 0; + } + int ret = BZ2_bzDecompress(¶m->bz); + if (ret == BZ_STREAM_END) { + param->zend = true; + if (param->bz.avail_in > 0) { + RNP_LOG("data beyond the end of z stream"); + } + break; + } + if (ret != BZ_OK) { + RNP_LOG("bzdecompress error %d", ret); + return false; + } + if (!param->bz.avail_in && src_eof(param->pkt.readsrc)) { + RNP_LOG("unexpected end of bzip stream"); + return false; + } + } + + param->inpos = (uint8_t *) param->bz.next_in - param->in; + *readres = len - param->bz.avail_out; + return true; + } +#endif + return false; +} + +static void +compressed_src_close(pgp_source_t *src) +{ + pgp_source_compressed_param_t *param = (pgp_source_compressed_param_t *) src->param; + if (!param) { + return; + } + + if (param->pkt.hdr.partial) { + src_close(param->pkt.readsrc); + free(param->pkt.readsrc); + param->pkt.readsrc = NULL; + } + +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + BZ2_bzDecompressEnd(¶m->bz); + } +#endif + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + inflateEnd(¶m->z); + } + + free(src->param); + src->param = NULL; +} + +#if defined(ENABLE_AEAD) +static bool +encrypted_start_aead_chunk(pgp_source_encrypted_param_t *param, size_t idx, bool last) +{ + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen; + + /* set chunk index for additional data */ + STORE64BE(param->aead_ad + param->aead_adlen - 8, idx); + + if (last) { + uint64_t total = idx * param->chunklen; + if (idx && param->chunkin) { + total -= param->chunklen - param->chunkin; + } + + if (!param->chunkin) { + /* reset the crypto in case we had empty chunk before the last one */ + pgp_cipher_aead_reset(¶m->decrypt); + } + STORE64BE(param->aead_ad + param->aead_adlen, total); + param->aead_adlen += 8; + } + + if (!pgp_cipher_aead_set_ad(¶m->decrypt, param->aead_ad, param->aead_adlen)) { + RNP_LOG("failed to set ad"); + return false; + } + + /* setup chunk */ + param->chunkidx = idx; + param->chunkin = 0; + + /* set chunk index for nonce */ + nlen = pgp_cipher_aead_nonce(param->aead_hdr.aalg, param->aead_hdr.iv, nonce, idx); + + /* start cipher */ + return pgp_cipher_aead_start(¶m->decrypt, nonce, nlen); +} + +/* read and decrypt bytes to the cache. Should be called only on empty cache. */ +static bool +encrypted_src_read_aead_part(pgp_source_encrypted_param_t *param) +{ + bool lastchunk = false; + bool chunkend = false; + bool res = false; + size_t read; + size_t tagread; + size_t taglen; + + param->cachepos = 0; + param->cachelen = 0; + + if (param->auth_validated) { + return true; + } + + /* it is always 16 for defined EAX and OCB, however this may change in future */ + taglen = pgp_cipher_aead_tag_len(param->aead_hdr.aalg); + read = sizeof(param->cache) - 2 * PGP_AEAD_MAX_TAG_LEN; + + if (read >= param->chunklen - param->chunkin) { + read = param->chunklen - param->chunkin; + chunkend = true; + } else { + read = read - read % pgp_cipher_aead_granularity(¶m->decrypt); + } + + if (!src_read(param->pkt.readsrc, param->cache, read, &read)) { + return false; + } + + /* checking whether we have enough input for the final tags */ + if (!src_peek(param->pkt.readsrc, param->cache + read, taglen * 2, &tagread)) { + return false; + } + + if (tagread < taglen * 2) { + /* this would mean the end of the stream */ + if ((param->chunkin == 0) && (read + tagread == taglen)) { + /* we have empty chunk and final tag */ + chunkend = false; + lastchunk = true; + } else if (read + tagread >= 2 * taglen) { + /* we have end of chunk and final tag */ + chunkend = true; + lastchunk = true; + } else { + RNP_LOG("unexpected end of data"); + return false; + } + } + + if (!chunkend && !lastchunk) { + param->chunkin += read; + res = pgp_cipher_aead_update(¶m->decrypt, param->cache, param->cache, read); + if (res) { + param->cachelen = read; + } + return res; + } + + if (chunkend) { + if (tagread > taglen) { + src_skip(param->pkt.readsrc, tagread - taglen); + } + + res = pgp_cipher_aead_finish( + ¶m->decrypt, param->cache, param->cache, read + tagread - taglen); + if (!res) { + RNP_LOG("failed to finalize aead chunk"); + return res; + } + param->cachelen = read + tagread - 2 * taglen; + param->chunkin += param->cachelen; + } + + size_t chunkidx = param->chunkidx; + if (chunkend && param->chunkin) { + chunkidx++; + } + + if (!(res = encrypted_start_aead_chunk(param, chunkidx, lastchunk))) { + RNP_LOG("failed to start aead chunk"); + return res; + } + + if (lastchunk) { + if (tagread > 0) { + src_skip(param->pkt.readsrc, tagread); + } + + size_t off = read + tagread - taglen; + res = pgp_cipher_aead_finish( + ¶m->decrypt, param->cache + off, param->cache + off, taglen); + if (!res) { + RNP_LOG("wrong last chunk"); + return res; + } + param->auth_validated = true; + } + + return res; +} +#endif + +static bool +encrypted_src_read_aead(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ +#if !defined(ENABLE_AEAD) + return false; +#else + pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; + size_t cbytes; + size_t left = len; + + do { + /* check whether we have something in the cache */ + cbytes = param->cachelen - param->cachepos; + if (cbytes > 0) { + if (cbytes >= left) { + memcpy(buf, param->cache + param->cachepos, left); + param->cachepos += left; + if (param->cachepos == param->cachelen) { + param->cachepos = param->cachelen = 0; + } + *read = len; + return true; + } + memcpy(buf, param->cache + param->cachepos, cbytes); + buf = (uint8_t *) buf + cbytes; + left -= cbytes; + param->cachepos = param->cachelen = 0; + } + + /* read something into cache */ + if (!encrypted_src_read_aead_part(param)) { + return false; + } + } while ((left > 0) && (param->cachelen > 0)); + + *read = len - left; + return true; +#endif +} + +static bool +encrypted_src_read_cfb(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; + if (param == NULL) { + return false; + } + + if (src->eof) { + *readres = 0; + return true; + } + + size_t read; + if (!src_read(param->pkt.readsrc, buf, len, &read)) { + return false; + } + if (!read) { + *readres = 0; + return true; + } + + bool parsemdc = false; + uint8_t mdcbuf[MDC_V1_SIZE]; + if (param->auth_type == rnp::AuthType::MDC) { + size_t mdcread = 0; + /* make sure there are always 22 bytes left on input */ + if (!src_peek(param->pkt.readsrc, mdcbuf, MDC_V1_SIZE, &mdcread) || + (mdcread + read < MDC_V1_SIZE)) { + RNP_LOG("wrong mdc read state"); + return false; + } + if (mdcread < MDC_V1_SIZE) { + src_skip(param->pkt.readsrc, mdcread); + size_t mdcsub = MDC_V1_SIZE - mdcread; + memmove(&mdcbuf[mdcsub], mdcbuf, mdcread); + memcpy(mdcbuf, (uint8_t *) buf + read - mdcsub, mdcsub); + read -= mdcsub; + parsemdc = true; + } + } + + pgp_cipher_cfb_decrypt(¶m->decrypt, (uint8_t *) buf, (uint8_t *) buf, read); + + if (param->auth_type == rnp::AuthType::MDC) { + try { + param->mdc->add(buf, read); + + if (parsemdc) { + pgp_cipher_cfb_decrypt(¶m->decrypt, mdcbuf, mdcbuf, MDC_V1_SIZE); + pgp_cipher_cfb_finish(¶m->decrypt); + param->mdc->add(mdcbuf, 2); + uint8_t hash[PGP_SHA1_HASH_SIZE] = {0}; + param->mdc->finish(hash); + param->mdc = nullptr; + + if ((mdcbuf[0] != MDC_PKT_TAG) || (mdcbuf[1] != MDC_V1_SIZE - 2)) { + RNP_LOG("mdc header check failed"); + return false; + } + + if (memcmp(&mdcbuf[2], hash, PGP_SHA1_HASH_SIZE) != 0) { + RNP_LOG("mdc hash check failed"); + return false; + } + param->auth_validated = true; + } + } catch (const std::exception &e) { + RNP_LOG("mdc update failed: %s", e.what()); + return false; + } + } + *readres = read; + return true; +} + +static rnp_result_t +encrypted_src_finish(pgp_source_t *src) +{ + pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; + + /* report to the handler that decryption is finished */ + if (param->handler->on_decryption_done) { + bool validated = (param->auth_type != rnp::AuthType::None) && param->auth_validated; + param->handler->on_decryption_done(validated, param->handler->param); + } + + if ((param->auth_type == rnp::AuthType::None) || param->auth_validated) { + return RNP_SUCCESS; + } + switch (param->auth_type) { + case rnp::AuthType::MDC: + RNP_LOG("mdc was not validated"); + break; + case rnp::AuthType::AEADv1: + RNP_LOG("aead last chunk was not validated"); + break; + default: + RNP_LOG("auth was not validated"); + break; + } + return RNP_ERROR_BAD_STATE; +} + +static void +encrypted_src_close(pgp_source_t *src) +{ + pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; + if (!param) { + return; + } + if (param->pkt.hdr.partial) { + src_close(param->pkt.readsrc); + free(param->pkt.readsrc); + param->pkt.readsrc = NULL; + } + + if (!param->use_cfb()) { +#if defined(ENABLE_AEAD) + pgp_cipher_aead_destroy(¶m->decrypt); +#endif + } else { + pgp_cipher_cfb_finish(¶m->decrypt); + } + + delete param; + src->param = NULL; +} + +static void +add_hash_for_sig(pgp_source_signed_param_t *param, pgp_sig_type_t stype, pgp_hash_alg_t halg) +{ + /* Cleartext always uses param->hashes instead of param->txt_hashes */ + if (!param->cleartext && (stype == PGP_SIG_TEXT)) { + param->txt_hashes.add_alg(halg); + } + param->hashes.add_alg(halg); +} + +static const rnp::Hash * +get_hash_for_sig(pgp_source_signed_param_t ¶m, pgp_signature_info_t &sinfo) +{ + /* Cleartext always uses param->hashes instead of param->txt_hashes */ + if (!param.cleartext && (sinfo.sig->type() == PGP_SIG_TEXT)) { + return param.txt_hashes.get(sinfo.sig->halg); + } + return param.hashes.get(sinfo.sig->halg); +} + +static void +signed_validate_signature(pgp_source_signed_param_t ¶m, pgp_signature_info_t &sinfo) +{ + /* Check signature type */ + if (!sinfo.sig->is_document()) { + RNP_LOG("Invalid document signature type: %d", (int) sinfo.sig->type()); + sinfo.valid = false; + return; + } + /* Find signing key */ + pgp_key_request_ctx_t keyctx(PGP_OP_VERIFY, false, PGP_KEY_SEARCH_FINGERPRINT); + + /* Get signer's fp or keyid */ + if (sinfo.sig->has_keyfp()) { + keyctx.search.by.fingerprint = sinfo.sig->keyfp(); + } else if (sinfo.sig->has_keyid()) { + keyctx.search.type = PGP_KEY_SEARCH_KEYID; + keyctx.search.by.keyid = sinfo.sig->keyid(); + } else { + RNP_LOG("cannot get signer's key fp or id from signature."); + sinfo.unknown = true; + return; + } + /* Get the public key */ + pgp_key_t *key = pgp_request_key(param.handler->key_provider, &keyctx); + if (!key) { + /* fallback to secret key */ + keyctx.secret = true; + if (!(key = pgp_request_key(param.handler->key_provider, &keyctx))) { + RNP_LOG("signer's key not found"); + sinfo.no_signer = true; + return; + } + } + try { + /* Get the hash context and clone it. */ + auto hash = get_hash_for_sig(param, sinfo); + if (!hash) { + RNP_LOG("failed to get hash context."); + return; + } + auto shash = hash->clone(); + key->validate_sig(sinfo, *shash, *param.handler->ctx->ctx); + } catch (const std::exception &e) { + RNP_LOG("Signature validation failed: %s", e.what()); + sinfo.valid = false; + } +} + +static long +stripped_line_len(uint8_t *begin, uint8_t *end) +{ + uint8_t *stripped_end = end; + + while (stripped_end >= begin && (*stripped_end == CH_CR || *stripped_end == CH_LF)) { + stripped_end--; + } + + return stripped_end - begin + 1; +} + +static void +signed_src_update(pgp_source_t *src, const void *buf, size_t len) +{ + if (!len) { + return; + } + /* check for extremely unlikely pointer overflow/wrap case */ + if (((uint8_t *) buf + len) < ((uint8_t *) buf + len - 1)) { + signed_src_update(src, buf, len - 1); + uint8_t last = *((uint8_t *) buf + len - 1); + signed_src_update(src, &last, 1); + } + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + try { + param->hashes.add(buf, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + /* update text-mode sig hashes */ + if (param->txt_hashes.hashes.empty()) { + return; + } + + uint8_t *ch = (uint8_t *) buf; + uint8_t *linebeg = ch; + uint8_t *end = (uint8_t *) buf + len; + /* we support LF and CRLF line endings */ + while (ch < end) { + /* continue if not reached LF */ + if (*ch != CH_LF) { + if (*ch != CH_CR && param->stripped_crs > 0) { + while (param->stripped_crs--) { + try { + param->txt_hashes.add(ST_CR, 1); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + } + param->stripped_crs = 0; + } + + if (!param->max_line_warn && param->text_line_len >= MAXIMUM_GNUPG_LINELEN) { + RNP_LOG("Canonical text document signature: line is too long, may cause " + "incompatibility with other implementations. Consider using binary " + "signature instead."); + param->max_line_warn = true; + } + + ch++; + param->text_line_len++; + continue; + } + /* reached eol: dump line contents */ + param->stripped_crs = 0; + param->text_line_len = 0; + if (ch > linebeg) { + long stripped_len = stripped_line_len(linebeg, ch); + if (stripped_len > 0) { + try { + param->txt_hashes.add(linebeg, stripped_len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + } + } + /* dump EOL */ + try { + param->txt_hashes.add(ST_CRLF, 2); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + ch++; + linebeg = ch; + } + /* check if we have undumped line contents */ + if (linebeg < end) { + long stripped_len = stripped_line_len(linebeg, end - 1); + if (stripped_len < end - linebeg) { + param->stripped_crs = end - linebeg - stripped_len; + } + if (stripped_len > 0) { + try { + param->txt_hashes.add(linebeg, stripped_len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + } + } +} + +static bool +signed_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + if (!param) { + return false; + } + return src_read(param->readsrc, buf, len, read); +} + +static void +signed_src_close(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + if (!param) { + return; + } + delete param; + src->param = NULL; +} + +#define MAX_SIGNATURES 16384 + +static rnp_result_t +signed_read_single_signature(pgp_source_signed_param_t *param, + pgp_source_t * readsrc, + pgp_signature_t ** sig) +{ + uint8_t ptag; + if (!src_peek_eq(readsrc, &ptag, 1)) { + RNP_LOG("failed to read signature packet header"); + return RNP_ERROR_READ; + } + + int ptype = get_packet_type(ptag); + if (ptype != PGP_PKT_SIGNATURE) { + RNP_LOG("unexpected packet %d", ptype); + return RNP_ERROR_BAD_FORMAT; + } + + if (param->siginfos.size() >= MAX_SIGNATURES) { + RNP_LOG("Too many signatures in the stream."); + return RNP_ERROR_BAD_FORMAT; + } + + try { + param->siginfos.emplace_back(); + pgp_signature_info_t &siginfo = param->siginfos.back(); + pgp_signature_t readsig; + if (readsig.parse(*readsrc)) { + RNP_LOG("failed to parse signature"); + siginfo.unknown = true; + if (sig) { + *sig = NULL; + } + return RNP_SUCCESS; + } + param->sigs.push_back(std::move(readsig)); + siginfo.sig = ¶m->sigs.back(); + if (sig) { + *sig = siginfo.sig; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } +} + +static rnp_result_t +signed_read_cleartext_signatures(pgp_source_t &src, pgp_source_signed_param_t *param) +{ + try { + rnp::ArmoredSource armor(*param->readsrc); + while (!armor.eof()) { + auto ret = signed_read_single_signature(param, &armor.src(), NULL); + if (ret) { + return ret; + } + } + return RNP_SUCCESS; + } catch (const rnp::rnp_exception &e) { + RNP_LOG("%s", e.what()); + return e.code(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_FORMAT; + } +} + +static rnp_result_t +signed_read_signatures(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + + /* reading signatures */ + for (auto op = param->onepasses.rbegin(); op != param->onepasses.rend(); op++) { + pgp_signature_t *sig = NULL; + rnp_result_t ret = signed_read_single_signature(param, src, &sig); + /* we have more onepasses then signatures */ + if (ret == RNP_ERROR_READ) { + RNP_LOG("Warning: premature end of signatures"); + return param->siginfos.size() ? RNP_SUCCESS : ret; + } + if (ret) { + return ret; + } + if (sig && !sig->matches_onepass(*op)) { + RNP_LOG("Warning: signature doesn't match one-pass"); + } + } + return RNP_SUCCESS; +} + +static rnp_result_t +signed_src_finish(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (param->cleartext) { + ret = signed_read_cleartext_signatures(*src, param); + } else { + ret = signed_read_signatures(src); + } + + if (ret) { + return ret; + } + + if (!src_eof(src)) { + RNP_LOG("warning: unexpected data on the stream end"); + } + + /* validating signatures */ + for (auto &sinfo : param->siginfos) { + if (!sinfo.sig) { + continue; + } + signed_validate_signature(*param, sinfo); + } + + /* checking the validation results */ + ret = RNP_ERROR_SIGNATURE_INVALID; + for (auto &sinfo : param->siginfos) { + if (sinfo.valid) { + /* If we have at least one valid signature then data is safe to process */ + ret = RNP_SUCCESS; + break; + } + } + + /* call the callback with signature infos */ + if (param->handler->on_signatures) { + param->handler->on_signatures(param->siginfos, param->handler->param); + } + return ret; +} + +/* + * str is a string to tokenize. + * delims is a string containing a list of delimiter characters. + * result is a container<string_type> that supports push_back. + */ +template <typename T> +static void +tokenize(const typename T::value_type &str, const typename T::value_type &delims, T &result) +{ + typedef typename T::value_type::size_type string_size_t; + const string_size_t npos = T::value_type::npos; + + result.clear(); + string_size_t current; + string_size_t next = 0; + do { + next = str.find_first_not_of(delims, next); + if (next == npos) { + break; + } + current = next; + next = str.find_first_of(delims, current); + string_size_t count = (next == npos) ? npos : (next - current); + result.push_back(str.substr(current, count)); + } while (next != npos); +} + +static bool +cleartext_parse_headers(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + char hdr[1024] = {0}; + char * hval; + pgp_hash_alg_t halg; + size_t hdrlen; + + do { + if (!src_peek_line(param->readsrc, hdr, sizeof(hdr), &hdrlen)) { + RNP_LOG("failed to peek line"); + return false; + } + + if (!hdrlen) { + break; + } + + if (rnp::is_blank_line(hdr, hdrlen)) { + src_skip(param->readsrc, hdrlen); + break; + } + + try { + if ((hdrlen >= 6) && !strncmp(hdr, ST_HEADER_HASH, 6)) { + hval = hdr + 6; + + std::string remainder = hval; + + const std::string delimiters = ", \t"; + std::vector<std::string> tokens; + + tokenize(remainder, delimiters, tokens); + + for (const auto &token : tokens) { + if ((halg = rnp::Hash::alg(token.c_str())) == PGP_HASH_UNKNOWN) { + RNP_LOG("unknown halg: %s", token.c_str()); + continue; + } + add_hash_for_sig(param, PGP_SIG_TEXT, halg); + } + } else { + RNP_LOG("unknown header '%s'", hdr); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + + src_skip(param->readsrc, hdrlen); + + if (!src_skip_eol(param->readsrc)) { + return false; + } + } while (1); + + /* we have exactly one empty line after the headers */ + return src_skip_eol(param->readsrc); +} + +static void +cleartext_process_line(pgp_source_t *src, const uint8_t *buf, size_t len, bool eol) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + uint8_t * bufen = (uint8_t *) buf + len - 1; + + /* check for dashes only if we are not in the middle */ + if (!param->clr_mline && (len > 0) && (buf[0] == CH_DASH)) { + if ((len > 1) && (buf[1] == CH_SPACE)) { + buf += 2; + len -= 2; + } else if ((len > 5) && !memcmp(buf, ST_DASHES, 5)) { + param->clr_eod = true; + return; + } else { + RNP_LOG("dash at the line begin"); + } + } + + /* hash eol if it is not the first line and we are not in the middle */ + if (!param->clr_fline && !param->clr_mline) { + /* we hash \r\n after the previous line to not hash the last eol before the sig */ + signed_src_update(src, ST_CRLF, 2); + } + + if (!len) { + return; + } + + if (len + param->outlen > sizeof(param->out)) { + RNP_LOG("wrong state"); + return; + } + + /* if we have eol after this line then strip trailing spaces and tabs */ + if (eol) { + for (; (bufen >= buf) && + ((*bufen == CH_SPACE) || (*bufen == CH_TAB) || (*bufen == CH_CR)); + bufen--) + ; + } + + if ((len = bufen + 1 - buf)) { + memcpy(param->out + param->outlen, buf, len); + param->outlen += len; + signed_src_update(src, buf, len); + } +} + +static bool +cleartext_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + if (!param) { + return false; + } + + uint8_t srcb[CT_BUF_LEN]; + uint8_t *cur, *en, *bg; + size_t read = 0; + size_t origlen = len; + + read = param->outlen - param->outpos; + if (read >= len) { + memcpy(buf, param->out + param->outpos, len); + param->outpos += len; + if (param->outpos == param->outlen) { + param->outpos = param->outlen = 0; + } + *readres = len; + return true; + } else if (read > 0) { + memcpy(buf, param->out + param->outpos, read); + len -= read; + buf = (uint8_t *) buf + read; + param->outpos = param->outlen = 0; + } + + if (param->clr_eod) { + *readres = origlen - len; + return true; + } + + do { + if (!src_peek(param->readsrc, srcb, sizeof(srcb), &read)) { + return false; + } else if (!read) { + break; + } + + /* processing data line by line, eol could be \n or \r\n */ + for (cur = srcb, bg = srcb, en = cur + read; cur < en; cur++) { + if ((*cur == CH_LF) || + ((*cur == CH_CR) && (cur + 1 < en) && (*(cur + 1) == CH_LF))) { + cleartext_process_line(src, bg, cur - bg, true); + /* processing eol */ + if (param->clr_eod) { + break; + } + + /* processing eol */ + param->clr_fline = false; + param->clr_mline = false; + if (*cur == CH_CR) { + param->out[param->outlen++] = *cur++; + } + param->out[param->outlen++] = *cur; + bg = cur + 1; + } + } + + /* if line is larger then 4k then just dump it out */ + if ((bg == srcb) && !param->clr_eod) { + /* if last char is \r, and it's not the end of stream, then do not dump it */ + if ((en > bg) && (*(en - 1) == CH_CR) && (read > 1)) { + en--; + } + cleartext_process_line(src, bg, en - bg, false); + param->clr_mline = true; + bg = en; + } + src_skip(param->readsrc, bg - srcb); + + /* put data from the param->out to buf */ + read = param->outlen > len ? len : param->outlen; + memcpy(buf, param->out, read); + buf = (uint8_t *) buf + read; + len -= read; + + if (read == param->outlen) { + param->outlen = 0; + } else { + param->outpos = read; + } + + /* we got to the signature marker */ + if (param->clr_eod || !len) { + break; + } + } while (1); + + *readres = origlen - len; + return true; +} + +static bool +encrypted_decrypt_cfb_header(pgp_source_encrypted_param_t *param, + pgp_symm_alg_t alg, + uint8_t * key) +{ + pgp_crypt_t crypt; + uint8_t enchdr[PGP_MAX_BLOCK_SIZE + 2]; + uint8_t dechdr[PGP_MAX_BLOCK_SIZE + 2]; + unsigned blsize; + + if (!(blsize = pgp_block_size(alg))) { + return false; + } + + /* reading encrypted header to check the password validity */ + if (!src_peek_eq(param->pkt.readsrc, enchdr, blsize + 2)) { + RNP_LOG("failed to read encrypted header"); + return false; + } + + /* having symmetric key in keybuf let's decrypt blocksize + 2 bytes and check them */ + if (!pgp_cipher_cfb_start(&crypt, alg, key, NULL)) { + RNP_LOG("failed to start cipher"); + return false; + } + + pgp_cipher_cfb_decrypt(&crypt, dechdr, enchdr, blsize + 2); + + if ((dechdr[blsize] != dechdr[blsize - 2]) || (dechdr[blsize + 1] != dechdr[blsize - 1])) { + RNP_LOG("checksum check failed"); + goto error; + } + + src_skip(param->pkt.readsrc, blsize + 2); + param->decrypt = crypt; + + /* init mdc if it is here */ + /* RFC 4880, 5.13: Unlike the Symmetrically Encrypted Data Packet, no special CFB + * resynchronization is done after encrypting this prefix data. */ + if (param->auth_type == rnp::AuthType::None) { + pgp_cipher_cfb_resync(¶m->decrypt, enchdr + 2); + return true; + } + + try { + param->mdc = rnp::Hash::create(PGP_HASH_SHA1); + param->mdc->add(dechdr, blsize + 2); + } catch (const std::exception &e) { + RNP_LOG("cannot create sha1 hash: %s", e.what()); + goto error; + } + return true; +error: + pgp_cipher_cfb_finish(&crypt); + return false; +} + +static bool +encrypted_start_aead(pgp_source_encrypted_param_t *param, pgp_symm_alg_t alg, uint8_t *key) +{ +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD is not enabled."); + return false; +#else + size_t gran; + + if (alg != param->aead_hdr.ealg) { + return false; + } + + /* initialize cipher with key */ + if (!pgp_cipher_aead_init( + ¶m->decrypt, param->aead_hdr.ealg, param->aead_hdr.aalg, key, true)) { + return false; + } + + gran = pgp_cipher_aead_granularity(¶m->decrypt); + if (gran > sizeof(param->cache)) { + RNP_LOG("wrong granularity"); + return false; + } + + return encrypted_start_aead_chunk(param, 0, false); +#endif +} + +static bool +encrypted_try_key(pgp_source_encrypted_param_t *param, + pgp_pk_sesskey_t * sesskey, + pgp_key_pkt_t * seckey, + rnp::SecurityContext & ctx) +{ + pgp_encrypted_material_t encmaterial; + try { + if (!sesskey->parse_material(encmaterial)) { + return false; + } + seckey->material.validate(ctx, false); + if (!seckey->material.valid()) { + RNP_LOG("Attempt to decrypt using the key with invalid material."); + return false; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + + rnp::secure_array<uint8_t, PGP_MPINT_SIZE> decbuf; + /* Decrypting session key value */ + rnp_result_t err; + bool res = false; + pgp_key_material_t *keymaterial = &seckey->material; + size_t declen = 0; + switch (sesskey->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + err = rsa_decrypt_pkcs1( + &ctx.rng, decbuf.data(), &declen, &encmaterial.rsa, &keymaterial->rsa); + if (err) { + RNP_LOG("RSA decryption failure"); + return false; + } + break; + case PGP_PKA_SM2: +#if defined(ENABLE_SM2) + declen = decbuf.size(); + err = sm2_decrypt(decbuf.data(), &declen, &encmaterial.sm2, &keymaterial->ec); + if (err != RNP_SUCCESS) { + RNP_LOG("SM2 decryption failure, error %x", (int) err); + return false; + } + break; +#else + RNP_LOG("SM2 decryption is not available."); + return false; +#endif + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: { + const rnp_result_t ret = elgamal_decrypt_pkcs1( + &ctx.rng, decbuf.data(), &declen, &encmaterial.eg, &keymaterial->eg); + if (ret) { + RNP_LOG("ElGamal decryption failure [%X]", ret); + return false; + } + break; + } + case PGP_PKA_ECDH: { + if (!curve_supported(keymaterial->ec.curve)) { + RNP_LOG("ECDH decrypt: curve %d is not supported.", (int) keymaterial->ec.curve); + return false; + } + pgp_fingerprint_t fingerprint; + if (pgp_fingerprint(fingerprint, *seckey)) { + RNP_LOG("ECDH fingerprint calculation failed"); + return false; + } + if ((keymaterial->ec.curve == PGP_CURVE_25519) && + !x25519_bits_tweaked(keymaterial->ec)) { + RNP_LOG("Warning: bits of 25519 secret key are not tweaked."); + } + declen = decbuf.size(); + err = ecdh_decrypt_pkcs5( + decbuf.data(), &declen, &encmaterial.ecdh, &keymaterial->ec, fingerprint); + if (err != RNP_SUCCESS) { + RNP_LOG("ECDH decryption error %u", err); + return false; + } + break; + } + default: + RNP_LOG("unsupported public key algorithm %d\n", seckey->alg); + return false; + } + + /* Check algorithm and key length */ + if (!pgp_is_sa_supported(decbuf[0])) { + RNP_LOG("Unsupported symmetric algorithm %" PRIu8, decbuf[0]); + return false; + } + + pgp_symm_alg_t salg = static_cast<pgp_symm_alg_t>(decbuf[0]); + size_t keylen = pgp_key_size(salg); + if (declen != keylen + 3) { + RNP_LOG("invalid symmetric key length"); + return false; + } + + /* Validate checksum */ + rnp::secure_array<unsigned, 1> checksum; + for (unsigned i = 1; i <= keylen; i++) { + checksum[0] += decbuf[i]; + } + + if ((checksum[0] & 0xffff) != + (decbuf[keylen + 2] | ((unsigned) decbuf[keylen + 1] << 8))) { + RNP_LOG("wrong checksum\n"); + return false; + } + + if (param->use_cfb()) { + /* Decrypt header */ + res = encrypted_decrypt_cfb_header(param, salg, &decbuf[1]); + } else { + /* Start AEAD decrypting, assuming we have correct key */ + res = encrypted_start_aead(param, salg, &decbuf[1]); + } + if (res) { + param->salg = salg; + } + return res; +} + +#if defined(ENABLE_AEAD) +static bool +encrypted_sesk_set_ad(pgp_crypt_t *crypt, pgp_sk_sesskey_t *skey) +{ + /* TODO: this method is exact duplicate as in stream-write.c. Not sure where to put it */ + uint8_t ad_data[4]; + + ad_data[0] = PGP_PKT_SK_SESSION_KEY | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + ad_data[1] = skey->version; + ad_data[2] = skey->alg; + ad_data[3] = skey->aalg; + + return pgp_cipher_aead_set_ad(crypt, ad_data, 4); +} +#endif + +static int +encrypted_try_password(pgp_source_encrypted_param_t *param, const char *password) +{ + bool keyavail = false; /* tried password at least once */ + + for (auto &skey : param->symencs) { + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE + 1> keybuf; + /* deriving symmetric key from password */ + size_t keysize = pgp_key_size(skey.alg); + if (!keysize || !pgp_s2k_derive_key(&skey.s2k, password, keybuf.data(), keysize)) { + continue; + } + pgp_crypt_t crypt; + pgp_symm_alg_t alg; + + if (skey.version == PGP_SKSK_V4) { + /* v4 symmetrically-encrypted session key */ + if (skey.enckeylen > 0) { + /* decrypting session key */ + if (!pgp_cipher_cfb_start(&crypt, skey.alg, keybuf.data(), NULL)) { + continue; + } + + pgp_cipher_cfb_decrypt(&crypt, keybuf.data(), skey.enckey, skey.enckeylen); + pgp_cipher_cfb_finish(&crypt); + + alg = (pgp_symm_alg_t) keybuf[0]; + keysize = pgp_key_size(alg); + if (!keysize || (keysize + 1 != skey.enckeylen)) { + continue; + } + memmove(keybuf.data(), keybuf.data() + 1, keysize); + } else { + alg = (pgp_symm_alg_t) skey.alg; + } + + if (!pgp_block_size(alg)) { + continue; + } + keyavail = true; + } else if (skey.version == PGP_SKSK_V5) { +#if !defined(ENABLE_AEAD) + continue; +#else + /* v5 AEAD-encrypted session key */ + size_t taglen = pgp_cipher_aead_tag_len(skey.aalg); + size_t ceklen = pgp_key_size(param->aead_hdr.ealg); + if (!taglen || !ceklen || (ceklen + taglen != skey.enckeylen)) { + RNP_LOG("CEK len/alg mismatch"); + continue; + } + alg = skey.alg; + + /* initialize cipher */ + if (!pgp_cipher_aead_init(&crypt, skey.alg, skey.aalg, keybuf.data(), true)) { + continue; + } + + /* set additional data */ + if (!encrypted_sesk_set_ad(&crypt, &skey)) { + RNP_LOG("failed to set ad"); + continue; + } + + /* calculate nonce */ + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t noncelen = pgp_cipher_aead_nonce(skey.aalg, skey.iv, nonce, 0); + + /* start cipher, decrypt key and verify tag */ + keyavail = + pgp_cipher_aead_start(&crypt, nonce, noncelen) && + pgp_cipher_aead_finish(&crypt, keybuf.data(), skey.enckey, skey.enckeylen); + pgp_cipher_aead_destroy(&crypt); + + /* we have decrypted key so let's start decryption */ + if (!keyavail) { + continue; + } +#endif + } else { + continue; + } + + /* Decrypt header for CFB */ + if (param->use_cfb() && !encrypted_decrypt_cfb_header(param, alg, keybuf.data())) { + continue; + } + if (!param->use_cfb() && + !encrypted_start_aead(param, param->aead_hdr.ealg, keybuf.data())) { + continue; + } + + param->salg = param->use_cfb() ? alg : param->aead_hdr.ealg; + /* inform handler that we used this symenc */ + if (param->handler->on_decryption_start) { + param->handler->on_decryption_start(NULL, &skey, param->handler->param); + } + return 1; + } + + if (!param->use_cfb() && pgp_block_size(param->aead_hdr.ealg)) { + /* we know aead symm alg even if we wasn't able to start decryption */ + param->salg = param->aead_hdr.ealg; + } + + if (!keyavail) { + RNP_LOG("no supported sk available"); + return -1; + } + return 0; +} + +/** @brief Initialize common to stream packets params, including partial data source */ +static rnp_result_t +init_packet_params(pgp_source_packet_param_t ¶m) +{ + param.origsrc = NULL; + + /* save packet header */ + rnp_result_t ret = stream_peek_packet_hdr(param.readsrc, ¶m.hdr); + if (ret) { + return ret; + } + src_skip(param.readsrc, param.hdr.hdr_len); + if (!param.hdr.partial) { + return RNP_SUCCESS; + } + + /* initialize partial reader if needed */ + pgp_source_t *partsrc = (pgp_source_t *) calloc(1, sizeof(*partsrc)); + if (!partsrc) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t errcode = init_partial_pkt_src(partsrc, param.readsrc, param.hdr); + if (errcode) { + free(partsrc); + return errcode; + } + param.origsrc = param.readsrc; + param.readsrc = partsrc; + return RNP_SUCCESS; +} + +rnp_result_t +init_literal_src(pgp_source_t *src, pgp_source_t *readsrc) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + pgp_source_literal_param_t *param; + uint8_t format = 0; + uint8_t nlen = 0; + uint8_t timestamp[4]; + + if (!init_src_common(src, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_source_literal_param_t *) src->param; + param->pkt.readsrc = readsrc; + src->read = literal_src_read; + src->close = literal_src_close; + src->type = PGP_STREAM_LITERAL; + + /* Reading packet length/checking whether it is partial */ + if ((ret = init_packet_params(param->pkt))) { + goto finish; + } + + /* data format */ + if (!src_read_eq(param->pkt.readsrc, &format, 1)) { + RNP_LOG("failed to read data format"); + ret = RNP_ERROR_READ; + goto finish; + } + + switch (format) { + case 'b': + case 't': + case 'u': + case 'l': + case '1': + break; + default: + RNP_LOG("unknown data format %" PRIu8, format); + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + param->hdr.format = format; + /* file name */ + if (!src_read_eq(param->pkt.readsrc, &nlen, 1)) { + RNP_LOG("failed to read file name length"); + ret = RNP_ERROR_READ; + goto finish; + } + if (nlen && !src_read_eq(param->pkt.readsrc, param->hdr.fname, nlen)) { + RNP_LOG("failed to read file name"); + ret = RNP_ERROR_READ; + goto finish; + } + param->hdr.fname[nlen] = 0; + param->hdr.fname_len = nlen; + /* timestamp */ + if (!src_read_eq(param->pkt.readsrc, timestamp, 4)) { + RNP_LOG("failed to read file timestamp"); + ret = RNP_ERROR_READ; + goto finish; + } + param->hdr.timestamp = read_uint32(timestamp); + + if (!param->pkt.hdr.indeterminate && !param->pkt.hdr.partial) { + /* format filename-length filename timestamp */ + const uint16_t nbytes = 1 + 1 + nlen + 4; + if (param->pkt.hdr.pkt_len < nbytes) { + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + src->size = param->pkt.hdr.pkt_len - nbytes; + src->knownsize = 1; + } + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + src_close(src); + } + return ret; +} + +bool +get_literal_src_hdr(pgp_source_t *src, pgp_literal_hdr_t *hdr) +{ + pgp_source_literal_param_t *param; + + if (src->type != PGP_STREAM_LITERAL) { + RNP_LOG("wrong stream"); + return false; + } + + param = (pgp_source_literal_param_t *) src->param; + *hdr = param->hdr; + return true; +} + +rnp_result_t +init_compressed_src(pgp_source_t *src, pgp_source_t *readsrc) +{ + rnp_result_t errcode = RNP_ERROR_GENERIC; + pgp_source_compressed_param_t *param; + uint8_t alg; + int zret; + + if (!init_src_common(src, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_source_compressed_param_t *) src->param; + param->pkt.readsrc = readsrc; + src->read = compressed_src_read; + src->close = compressed_src_close; + src->type = PGP_STREAM_COMPRESSED; + + /* Reading packet length/checking whether it is partial */ + errcode = init_packet_params(param->pkt); + if (errcode != RNP_SUCCESS) { + goto finish; + } + + /* Reading compression algorithm */ + if (!src_read_eq(param->pkt.readsrc, &alg, 1)) { + RNP_LOG("failed to read compression algorithm"); + errcode = RNP_ERROR_READ; + goto finish; + } + + /* Initializing decompression */ + switch (alg) { + case PGP_C_NONE: + break; + case PGP_C_ZIP: + case PGP_C_ZLIB: + (void) memset(¶m->z, 0x0, sizeof(param->z)); + zret = + alg == PGP_C_ZIP ? (int) inflateInit2(¶m->z, -15) : (int) inflateInit(¶m->z); + if (zret != Z_OK) { + RNP_LOG("failed to init zlib, error %d", zret); + errcode = RNP_ERROR_READ; + goto finish; + } + break; +#ifdef HAVE_BZLIB_H + case PGP_C_BZIP2: + (void) memset(¶m->bz, 0x0, sizeof(param->bz)); + zret = BZ2_bzDecompressInit(¶m->bz, 0, 0); + if (zret != BZ_OK) { + RNP_LOG("failed to init bz, error %d", zret); + errcode = RNP_ERROR_READ; + goto finish; + } + break; +#endif + default: + RNP_LOG("unknown compression algorithm: %d", (int) alg); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + param->alg = (pgp_compression_type_t) alg; + param->inlen = 0; + param->inpos = 0; + + errcode = RNP_SUCCESS; +finish: + if (errcode != RNP_SUCCESS) { + src_close(src); + } + return errcode; +} + +bool +get_compressed_src_alg(pgp_source_t *src, uint8_t *alg) +{ + pgp_source_compressed_param_t *param; + + if (src->type != PGP_STREAM_COMPRESSED) { + RNP_LOG("wrong stream"); + return false; + } + + param = (pgp_source_compressed_param_t *) src->param; + *alg = param->alg; + return true; +} + +bool +get_aead_src_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr) +{ + uint8_t hdrbt[4] = {0}; + + if (!src_read_eq(src, hdrbt, 4)) { + return false; + } + + hdr->version = hdrbt[0]; + hdr->ealg = (pgp_symm_alg_t) hdrbt[1]; + hdr->aalg = (pgp_aead_alg_t) hdrbt[2]; + hdr->csize = hdrbt[3]; + + if (!(hdr->ivlen = pgp_cipher_aead_nonce_len(hdr->aalg))) { + RNP_LOG("wrong aead nonce length: alg %d", (int) hdr->aalg); + return false; + } + + return src_read_eq(src, hdr->iv, hdr->ivlen); +} + +#define MAX_RECIPIENTS 16384 + +static rnp_result_t +encrypted_read_packet_data(pgp_source_encrypted_param_t *param) +{ + int ptype; + /* Reading pk/sk encrypted session key(s) */ + try { + size_t errors = 0; + bool stop = false; + while (!stop) { + if (param->pubencs.size() + param->symencs.size() + errors > MAX_RECIPIENTS) { + RNP_LOG("Too many recipients of the encrypted message. Aborting."); + return RNP_ERROR_BAD_STATE; + } + uint8_t ptag; + if (!src_peek_eq(param->pkt.readsrc, &ptag, 1)) { + RNP_LOG("failed to read packet header"); + return RNP_ERROR_READ; + } + ptype = get_packet_type(ptag); + switch (ptype) { + case PGP_PKT_SK_SESSION_KEY: { + pgp_sk_sesskey_t skey; + rnp_result_t ret = skey.parse(*param->pkt.readsrc); + if (ret == RNP_ERROR_READ) { + RNP_LOG("SKESK: Premature end of data."); + return ret; + } + if (ret) { + RNP_LOG("Failed to parse SKESK, skipping."); + errors++; + continue; + } + param->symencs.push_back(skey); + break; + } + case PGP_PKT_PK_SESSION_KEY: { + pgp_pk_sesskey_t pkey; + rnp_result_t ret = pkey.parse(*param->pkt.readsrc); + if (ret == RNP_ERROR_READ) { + RNP_LOG("PKESK: Premature end of data."); + return ret; + } + if (ret) { + RNP_LOG("Failed to parse PKESK, skipping."); + errors++; + continue; + } + param->pubencs.push_back(pkey); + break; + } + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_AEAD_ENCRYPTED: + stop = true; + break; + default: + RNP_LOG("unknown packet type: %d", ptype); + return RNP_ERROR_BAD_FORMAT; + } + } + } catch (const rnp::rnp_exception &e) { + RNP_LOG("%s: %d", e.what(), e.code()); + return e.code(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } + + /* Reading packet length/checking whether it is partial */ + rnp_result_t errcode = init_packet_params(param->pkt); + if (errcode) { + return errcode; + } + + /* Reading header of encrypted packet */ + if (ptype == PGP_PKT_AEAD_ENCRYPTED) { + param->auth_type = rnp::AuthType::AEADv1; + uint8_t hdr[4]; + if (!src_peek_eq(param->pkt.readsrc, hdr, 4)) { + return RNP_ERROR_READ; + } + + if (!get_aead_src_hdr(param->pkt.readsrc, ¶m->aead_hdr)) { + RNP_LOG("failed to read AEAD header"); + return RNP_ERROR_READ; + } + + /* check AEAD encrypted data packet header */ + if (param->aead_hdr.version != 1) { + RNP_LOG("unknown aead ver: %d", param->aead_hdr.version); + return RNP_ERROR_BAD_FORMAT; + } + if ((param->aead_hdr.aalg != PGP_AEAD_EAX) && (param->aead_hdr.aalg != PGP_AEAD_OCB)) { + RNP_LOG("unknown aead alg: %d", (int) param->aead_hdr.aalg); + return RNP_ERROR_BAD_FORMAT; + } + if (param->aead_hdr.csize > 56) { + RNP_LOG("too large chunk size: %d", param->aead_hdr.csize); + return RNP_ERROR_BAD_FORMAT; + } + if (param->aead_hdr.csize > 16) { + RNP_LOG("Warning: AEAD chunk bits > 16."); + } + param->chunklen = 1L << (param->aead_hdr.csize + 6); + + /* build additional data */ + param->aead_adlen = 13; + param->aead_ad[0] = param->pkt.hdr.hdr[0]; + memcpy(param->aead_ad + 1, hdr, 4); + memset(param->aead_ad + 5, 0, 8); + } else if (ptype == PGP_PKT_SE_IP_DATA) { + uint8_t mdcver; + if (!src_read_eq(param->pkt.readsrc, &mdcver, 1)) { + return RNP_ERROR_READ; + } + + if (mdcver != 1) { + RNP_LOG("unknown mdc ver: %d", (int) mdcver); + return RNP_ERROR_BAD_FORMAT; + } + param->auth_type = rnp::AuthType::MDC; + } + param->auth_validated = false; + + return RNP_SUCCESS; +} + +#define MAX_HIDDEN_TRIES 64 + +static rnp_result_t +init_encrypted_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc) +{ + if (!init_src_common(src, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_source_encrypted_param_t *param = new (std::nothrow) pgp_source_encrypted_param_t(); + if (!param) { + return RNP_ERROR_OUT_OF_MEMORY; + } + src->param = param; + param->pkt.readsrc = readsrc; + param->handler = handler; + + src->close = encrypted_src_close; + src->finish = encrypted_src_finish; + src->type = PGP_STREAM_ENCRYPTED; + + /* Read the packet-related information */ + rnp_result_t errcode = encrypted_read_packet_data(param); + if (errcode) { + goto finish; + } + + src->read = !param->use_cfb() ? encrypted_src_read_aead : encrypted_src_read_cfb; + + /* Obtaining the symmetric key */ + if (!handler->password_provider) { + RNP_LOG("no password provider"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + /* informing handler about the available pubencs/symencs */ + if (handler->on_recipients) { + handler->on_recipients(param->pubencs, param->symencs, handler->param); + } + + bool have_key; + have_key = false; + /* Trying public-key decryption */ + if (!param->pubencs.empty()) { + if (!handler->key_provider) { + RNP_LOG("no key provider"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + pgp_key_request_ctx_t keyctx(PGP_OP_DECRYPT, true, PGP_KEY_SEARCH_KEYID); + + size_t pubidx = 0; + size_t hidden_tries = 0; + errcode = RNP_ERROR_NO_SUITABLE_KEY; + while (pubidx < param->pubencs.size()) { + auto &pubenc = param->pubencs[pubidx]; + keyctx.search.by.keyid = pubenc.key_id; + /* Get the key if any */ + pgp_key_t *seckey = pgp_request_key(handler->key_provider, &keyctx); + if (!seckey) { + pubidx++; + continue; + } + /* Check whether key fits our needs */ + bool hidden = pubenc.key_id == pgp_key_id_t({}); + if (!hidden || (++hidden_tries >= MAX_HIDDEN_TRIES)) { + pubidx++; + } + if (!seckey->has_secret() || !seckey->can_encrypt()) { + continue; + } + /* Check whether key is of required algorithm for hidden keyid */ + if (hidden && seckey->alg() != pubenc.alg) { + continue; + } + /* Decrypt key */ + rnp::KeyLocker seclock(*seckey); + if (!seckey->unlock(*handler->password_provider, PGP_OP_DECRYPT)) { + errcode = RNP_ERROR_BAD_PASSWORD; + continue; + } + + /* Try to initialize the decryption */ + rnp::LogStop logstop(hidden); + if (encrypted_try_key(param, &pubenc, &seckey->pkt(), *handler->ctx->ctx)) { + have_key = true; + /* inform handler that we used this pubenc */ + if (handler->on_decryption_start) { + handler->on_decryption_start(&pubenc, NULL, handler->param); + } + break; + } + } + } + + /* Trying password-based decryption */ + if (!have_key && !param->symencs.empty()) { + rnp::secure_array<char, MAX_PASSWORD_LENGTH> password; + pgp_password_ctx_t pass_ctx(PGP_OP_DECRYPT_SYM); + if (!pgp_request_password( + handler->password_provider, &pass_ctx, password.data(), password.size())) { + errcode = RNP_ERROR_BAD_PASSWORD; + goto finish; + } + + int intres = encrypted_try_password(param, password.data()); + if (intres > 0) { + have_key = true; + } else if (intres < 0) { + errcode = RNP_ERROR_NOT_SUPPORTED; + } else { + errcode = RNP_ERROR_BAD_PASSWORD; + } + } + + /* report decryption start to the handler */ + if (handler->on_decryption_info) { + handler->on_decryption_info(param->auth_type == rnp::AuthType::MDC, + param->aead_hdr.aalg, + param->salg, + handler->param); + } + + if (!have_key) { + RNP_LOG("failed to obtain decrypting key or password"); + if (!errcode) { + errcode = RNP_ERROR_NO_SUITABLE_KEY; + } + goto finish; + } + errcode = RNP_SUCCESS; +finish: + if (errcode != RNP_SUCCESS) { + src_close(src); + } + return errcode; +} + +static rnp_result_t +init_cleartext_signed_src(pgp_source_t *src) +{ + char buf[64]; + size_t hdrlen = strlen(ST_CLEAR_BEGIN); + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + + /* checking header line */ + if (!src_read_eq(param->readsrc, buf, hdrlen)) { + RNP_LOG("failed to read header"); + return RNP_ERROR_READ; + } + + if (memcmp(ST_CLEAR_BEGIN, buf, hdrlen)) { + RNP_LOG("wrong header"); + return RNP_ERROR_BAD_FORMAT; + } + + /* eol */ + if (!src_skip_eol(param->readsrc)) { + RNP_LOG("no eol after the cleartext header"); + return RNP_ERROR_BAD_FORMAT; + } + + /* parsing Hash headers */ + if (!cleartext_parse_headers(src)) { + return RNP_ERROR_BAD_FORMAT; + } + + /* now we are good to go */ + param->clr_fline = true; + return RNP_SUCCESS; +} + +#define MAX_SIG_ERRORS 65536 + +static rnp_result_t +init_signed_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc) +{ + rnp_result_t errcode = RNP_ERROR_GENERIC; + pgp_source_signed_param_t *param; + uint8_t ptag; + int ptype; + pgp_signature_t * sig = NULL; + bool cleartext; + size_t sigerrors = 0; + + if (!init_src_common(src, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + try { + param = new pgp_source_signed_param_t(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + src->param = param; + + cleartext = is_cleartext_source(readsrc); + param->readsrc = readsrc; + param->handler = handler; + param->cleartext = cleartext; + param->stripped_crs = 0; + src->read = cleartext ? cleartext_src_read : signed_src_read; + src->close = signed_src_close; + src->finish = signed_src_finish; + src->type = cleartext ? PGP_STREAM_CLEARTEXT : PGP_STREAM_SIGNED; + + /* we need key provider to validate signatures */ + if (!handler->key_provider) { + RNP_LOG("no key provider"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if (cleartext) { + errcode = init_cleartext_signed_src(src); + goto finish; + } + + /* Reading one-pass and signature packets */ + while (true) { + /* stop early if we are in zip-bomb with erroneous packets */ + if (sigerrors >= MAX_SIG_ERRORS) { + RNP_LOG("Too many one-pass/signature errors. Stopping."); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + + size_t readb = readsrc->readb; + if (!src_peek_eq(readsrc, &ptag, 1)) { + RNP_LOG("failed to read packet header"); + errcode = RNP_ERROR_READ; + goto finish; + } + + ptype = get_packet_type(ptag); + + if (ptype == PGP_PKT_ONE_PASS_SIG) { + if (param->onepasses.size() >= MAX_SIGNATURES) { + RNP_LOG("Too many one-pass signatures."); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + pgp_one_pass_sig_t onepass; + try { + errcode = onepass.parse(*readsrc); + } catch (const std::exception &e) { + errcode = RNP_ERROR_GENERIC; + } + if (errcode) { + if (errcode == RNP_ERROR_READ) { + goto finish; + } + if (readb == readsrc->readb) { + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + sigerrors++; + continue; + } + + try { + param->onepasses.push_back(onepass); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + errcode = RNP_ERROR_OUT_OF_MEMORY; + goto finish; + } + + /* adding hash context */ + try { + add_hash_for_sig(param, onepass.type, onepass.halg); + } catch (const std::exception &e) { + RNP_LOG("Failed to create hash %d for onepass %d : %s.", + (int) onepass.halg, + (int) onepass.type, + e.what()); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if (onepass.nested) { + /* despite the name non-zero value means that it is the last one-pass */ + break; + } + } else if (ptype == PGP_PKT_SIGNATURE) { + /* no need to check the error here - we already know tag */ + if (signed_read_single_signature(param, readsrc, &sig)) { + sigerrors++; + } + /* adding hash context */ + if (sig) { + try { + add_hash_for_sig(param, sig->type(), sig->halg); + } catch (const std::exception &e) { + RNP_LOG("Failed to create hash %d for sig %d : %s.", + (int) sig->halg, + (int) sig->type(), + e.what()); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + } + } else { + break; + } + + /* check if we are not it endless loop */ + if (readb == readsrc->readb) { + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + /* for detached signature we'll get eof */ + if (src_eof(readsrc)) { + param->detached = true; + break; + } + } + + /* checking what we have now */ + if (param->onepasses.empty() && param->sigs.empty()) { + RNP_LOG("no signatures"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + if (!param->onepasses.empty() && !param->sigs.empty()) { + RNP_LOG("warning: one-passes are mixed with signatures"); + } + + errcode = RNP_SUCCESS; +finish: + if (errcode != RNP_SUCCESS) { + src_close(src); + } + + return errcode; +} + +pgp_processing_ctx_t::~pgp_processing_ctx_t() +{ + for (auto &src : sources) { + src_close(&src); + } +} + +/** @brief build PGP source sequence down to the literal data packet + * + **/ +static rnp_result_t +init_packet_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src) +{ + pgp_source_t *lsrc = &src; + size_t srcnum = ctx.sources.size(); + + while (1) { + uint8_t ptag = 0; + if (!src_peek_eq(lsrc, &ptag, 1)) { + RNP_LOG("cannot read packet tag"); + return RNP_ERROR_READ; + } + + int type = get_packet_type(ptag); + if (type < 0) { + RNP_LOG("wrong pkt tag %d", (int) ptag); + return RNP_ERROR_BAD_FORMAT; + } + + if (ctx.sources.size() - srcnum == MAXIMUM_NESTING_LEVEL) { + RNP_LOG("Too many nested OpenPGP packets"); + return RNP_ERROR_BAD_FORMAT; + } + + pgp_source_t psrc = {}; + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + switch (type) { + case PGP_PKT_PK_SESSION_KEY: + case PGP_PKT_SK_SESSION_KEY: + ret = init_encrypted_src(&ctx.handler, &psrc, lsrc); + break; + case PGP_PKT_ONE_PASS_SIG: + case PGP_PKT_SIGNATURE: + ret = init_signed_src(&ctx.handler, &psrc, lsrc); + break; + case PGP_PKT_COMPRESSED: + ret = init_compressed_src(&psrc, lsrc); + break; + case PGP_PKT_LITDATA: + if ((lsrc != &src) && (lsrc->type != PGP_STREAM_ENCRYPTED) && + (lsrc->type != PGP_STREAM_SIGNED) && (lsrc->type != PGP_STREAM_COMPRESSED)) { + RNP_LOG("unexpected literal pkt"); + ret = RNP_ERROR_BAD_FORMAT; + break; + } + ret = init_literal_src(&psrc, lsrc); + break; + case PGP_PKT_MARKER: + if (ctx.sources.size() != srcnum) { + RNP_LOG("Warning: marker packet wrapped in pgp stream."); + } + ret = stream_parse_marker(*lsrc); + if (ret) { + RNP_LOG("Invalid marker packet"); + return ret; + } + continue; + default: + RNP_LOG("unexpected pkt %d", type); + ret = RNP_ERROR_BAD_FORMAT; + } + + if (ret) { + return ret; + } + + try { + ctx.sources.push_back(psrc); + lsrc = &ctx.sources.back(); + } catch (const std::exception &e) { + src_close(&psrc); + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (lsrc->type == PGP_STREAM_LITERAL) { + ctx.literal_src = lsrc; + ctx.msg_type = PGP_MESSAGE_NORMAL; + return RNP_SUCCESS; + } + if (lsrc->type == PGP_STREAM_SIGNED) { + ctx.signed_src = lsrc; + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) lsrc->param; + if (param->detached) { + ctx.msg_type = PGP_MESSAGE_DETACHED; + return RNP_SUCCESS; + } + } + } +} + +static rnp_result_t +init_cleartext_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src) +{ + pgp_source_t clrsrc = {}; + rnp_result_t res; + + if ((res = init_signed_src(&ctx.handler, &clrsrc, &src))) { + return res; + } + try { + ctx.sources.push_back(clrsrc); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + src_close(&clrsrc); + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +static rnp_result_t +init_armored_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src) +{ + pgp_source_t armorsrc = {}; + rnp_result_t res; + + if ((res = init_armored_src(&armorsrc, &src))) { + return res; + } + + try { + ctx.sources.push_back(armorsrc); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + src_close(&armorsrc); + return RNP_ERROR_OUT_OF_MEMORY; + } + return init_packet_sequence(ctx, ctx.sources.back()); +} + +rnp_result_t +process_pgp_source(pgp_parse_handler_t *handler, pgp_source_t &src) +{ + rnp_result_t res = RNP_ERROR_BAD_FORMAT; + rnp_result_t fres; + pgp_processing_ctx_t ctx = {}; + pgp_source_t * decsrc = NULL; + pgp_source_t datasrc = {0}; + pgp_dest_t * outdest = NULL; + bool closeout = true; + uint8_t * readbuf = NULL; + + ctx.handler = *handler; + /* Building readers sequence. Checking whether it is binary data */ + if (is_pgp_source(src)) { + res = init_packet_sequence(ctx, src); + } else { + /* Trying armored or cleartext data */ + if (is_cleartext_source(&src)) { + /* Initializing cleartext message */ + res = init_cleartext_sequence(ctx, src); + } else if (is_armored_source(&src)) { + /* Initializing armored message */ + res = init_armored_sequence(ctx, src); + } else { + RNP_LOG("not an OpenPGP data provided"); + res = RNP_ERROR_BAD_FORMAT; + goto finish; + } + } + + if (res != RNP_SUCCESS) { + goto finish; + } + + if ((readbuf = (uint8_t *) calloc(1, PGP_INPUT_CACHE_SIZE)) == NULL) { + RNP_LOG("allocation failure"); + res = RNP_ERROR_OUT_OF_MEMORY; + goto finish; + } + + if (ctx.msg_type == PGP_MESSAGE_DETACHED) { + /* detached signature case */ + if (!handler->ctx->detached) { + RNP_LOG("Unexpected detached signature input."); + res = RNP_ERROR_BAD_STATE; + goto finish; + } + if (!handler->src_provider || !handler->src_provider(handler, &datasrc)) { + RNP_LOG("no data source for detached signature verification"); + res = RNP_ERROR_READ; + goto finish; + } + + while (!datasrc.eof) { + size_t read = 0; + if (!src_read(&datasrc, readbuf, PGP_INPUT_CACHE_SIZE, &read)) { + res = RNP_ERROR_GENERIC; + break; + } + if (read > 0) { + signed_src_update(ctx.signed_src, readbuf, read); + } + } + src_close(&datasrc); + } else { + if (handler->ctx->detached) { + RNP_LOG("Detached signature expected."); + res = RNP_ERROR_BAD_STATE; + goto finish; + } + /* file processing case */ + decsrc = &ctx.sources.back(); + char * filename = NULL; + uint32_t mtime = 0; + + if (ctx.literal_src) { + auto *param = static_cast<pgp_source_literal_param_t *>(ctx.literal_src->param); + filename = param->hdr.fname; + mtime = param->hdr.timestamp; + } + + if (!handler->dest_provider || + !handler->dest_provider(handler, &outdest, &closeout, filename, mtime)) { + res = RNP_ERROR_WRITE; + goto finish; + } + + /* reading the input */ + while (!decsrc->eof) { + size_t read = 0; + if (!src_read(decsrc, readbuf, PGP_INPUT_CACHE_SIZE, &read)) { + res = RNP_ERROR_GENERIC; + break; + } + if (!read) { + continue; + } + if (ctx.signed_src) { + signed_src_update(ctx.signed_src, readbuf, read); + } + dst_write(outdest, readbuf, read); + if (outdest->werr != RNP_SUCCESS) { + RNP_LOG("failed to output data"); + res = RNP_ERROR_WRITE; + break; + } + } + } + + /* finalizing the input. Signatures are checked on this step */ + if (res == RNP_SUCCESS) { + for (auto &ctxsrc : ctx.sources) { + fres = src_finish(&ctxsrc); + if (fres) { + res = fres; + } + } + } + + if (closeout && (ctx.msg_type != PGP_MESSAGE_DETACHED)) { + dst_close(outdest, res != RNP_SUCCESS); + } + +finish: + free(readbuf); + return res; +} diff --git a/src/librepgp/stream-parse.h b/src/librepgp/stream-parse.h new file mode 100644 index 0000000..4f22b9a --- /dev/null +++ b/src/librepgp/stream-parse.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_PARSE_H_ +#define STREAM_PARSE_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "rnp.h" +#include "stream-common.h" +#include "stream-ctx.h" +#include "stream-packet.h" + +typedef struct pgp_parse_handler_t pgp_parse_handler_t; +typedef struct pgp_signature_info_t pgp_signature_info_t; +typedef bool pgp_destination_func_t(pgp_parse_handler_t *handler, + pgp_dest_t ** dst, + bool * closedst, + const char * filename, + uint32_t mtime); +typedef bool pgp_source_func_t(pgp_parse_handler_t *handler, pgp_source_t *src); +typedef void pgp_signatures_func_t(const std::vector<pgp_signature_info_t> &sigs, void *param); + +typedef void pgp_on_recipients_func_t(const std::vector<pgp_pk_sesskey_t> &recipients, + const std::vector<pgp_sk_sesskey_t> &passwords, + void * param); +typedef void pgp_decryption_start_func_t(pgp_pk_sesskey_t *pubenc, + pgp_sk_sesskey_t *symenc, + void * param); +typedef void pgp_decryption_info_func_t(bool mdc, + pgp_aead_alg_t aead, + pgp_symm_alg_t salg, + void * param); +typedef void pgp_decryption_done_func_t(bool validated, void *param); + +/* handler used to return needed information during pgp source processing */ +typedef struct pgp_parse_handler_t { + pgp_password_provider_t *password_provider; /* if NULL then default will be used */ + pgp_key_provider_t * key_provider; /* must be set when key is required, i.e. during + signing/verification/public key encryption and + deryption */ + pgp_destination_func_t *dest_provider; /* called when destination stream is required */ + pgp_source_func_t * src_provider; /* required to provider source during the detached + signature verification */ + pgp_on_recipients_func_t * on_recipients; /* called before decryption start */ + pgp_decryption_start_func_t *on_decryption_start; /* called when decryption key obtained */ + pgp_decryption_info_func_t * on_decryption_info; /* called when decryption is started */ + pgp_decryption_done_func_t * on_decryption_done; /* called when decryption is finished */ + pgp_signatures_func_t * on_signatures; /* for signature verification results */ + + rnp_ctx_t *ctx; /* operation context */ + void * param; /* additional parameters */ +} pgp_parse_handler_t; + +/* @brief Process the OpenPGP source: file, memory, stdin + * Function will parse input data, provided by any source conforming to pgp_source_t, + * autodetecting whether it is armored, cleartext or binary. + * @param handler handler to respond on stream reader callbacks + * @param src initialized source with cache + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t process_pgp_source(pgp_parse_handler_t *handler, pgp_source_t &src); + +/* @brief Init source with OpenPGP compressed data packet + * @param src allocated pgp_source_t structure + * @param readsrc source to read compressed data from + * @return RNP_SUCCESS on success or error code otherwise + */ +rnp_result_t init_compressed_src(pgp_source_t *src, pgp_source_t *readsrc); + +/* @brief Get compression algorithm used in compressed source + * @param src compressed source, initialized with init_compressed_src + * @param alg algorithm will be written here. Cannot be NULL. + * @return true if operation succeeded and alg is populate or false otherwise + */ +bool get_compressed_src_alg(pgp_source_t *src, uint8_t *alg); + +/* @brief Init source with OpenPGP literal data packet + * @param src allocated pgp_source_t structure + * @param readsrc source to read literal data from + * @return RNP_SUCCESS on success or error code otherwise + */ +rnp_result_t init_literal_src(pgp_source_t *src, pgp_source_t *readsrc); + +/* @brief Get the literal data packet information fields (not the OpenPGP packet header) + * @param src literal data source, initialized with init_literal_src + * @param hdr pointer to header structure, where result will be stored + * @return true on success or false otherwise + */ +bool get_literal_src_hdr(pgp_source_t *src, pgp_literal_hdr_t *hdr); + +/* @brief Get the AEAD-encrypted packet information fields (not the OpenPGP packet header) + * @param src AEAD-encrypted data source (starting from packet data itself, not the header) + * @param hdr pointer to header structure, where result will be stored + * @return true on success or false otherwise + */ +bool get_aead_src_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr); + +#endif diff --git a/src/librepgp/stream-sig.cpp b/src/librepgp/stream-sig.cpp new file mode 100644 index 0000000..6f3bc81 --- /dev/null +++ b/src/librepgp/stream-sig.cpp @@ -0,0 +1,1557 @@ +/* + * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include <type_traits> +#include <stdexcept> +#include <rnp/rnp_def.h> +#include "types.h" +#include "stream-sig.h" +#include "stream-packet.h" +#include "stream-armor.h" +#include "pgp-key.h" +#include "crypto/signatures.h" + +#include <time.h> + +void +signature_hash_key(const pgp_key_pkt_t &key, rnp::Hash &hash) +{ + uint8_t hdr[3] = {0x99, 0x00, 0x00}; + if (key.hashed_data) { + write_uint16(hdr + 1, key.hashed_len); + hash.add(hdr, 3); + hash.add(key.hashed_data, key.hashed_len); + return; + } + + /* call self recursively if hashed data is not filled, to overcome const restriction */ + pgp_key_pkt_t keycp(key, true); + keycp.fill_hashed_data(); + signature_hash_key(keycp, hash); +} + +void +signature_hash_userid(const pgp_userid_pkt_t &uid, rnp::Hash &hash, pgp_version_t sigver) +{ + if (sigver < PGP_V4) { + hash.add(uid.uid, uid.uid_len); + return; + } + + uint8_t hdr[5] = {0}; + switch (uid.tag) { + case PGP_PKT_USER_ID: + hdr[0] = 0xB4; + break; + case PGP_PKT_USER_ATTR: + hdr[0] = 0xD1; + break; + default: + RNP_LOG("wrong uid"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + STORE32BE(hdr + 1, uid.uid_len); + hash.add(hdr, 5); + hash.add(uid.uid, uid.uid_len); +} + +std::unique_ptr<rnp::Hash> +signature_hash_certification(const pgp_signature_t & sig, + const pgp_key_pkt_t & key, + const pgp_userid_pkt_t &userid) +{ + auto hash = signature_init(key.material, sig.halg); + signature_hash_key(key, *hash); + signature_hash_userid(userid, *hash, sig.version); + return hash; +} + +std::unique_ptr<rnp::Hash> +signature_hash_binding(const pgp_signature_t &sig, + const pgp_key_pkt_t & key, + const pgp_key_pkt_t & subkey) +{ + auto hash = signature_init(key.material, sig.halg); + signature_hash_key(key, *hash); + signature_hash_key(subkey, *hash); + return hash; +} + +std::unique_ptr<rnp::Hash> +signature_hash_direct(const pgp_signature_t &sig, const pgp_key_pkt_t &key) +{ + auto hash = signature_init(key.material, sig.halg); + signature_hash_key(key, *hash); + return hash; +} + +rnp_result_t +process_pgp_signatures(pgp_source_t &src, pgp_signature_list_t &sigs) +{ + sigs.clear(); + /* Allow binary or armored input, including multiple armored messages */ + rnp::ArmoredSource armor( + src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + /* read sequence of OpenPGP signatures */ + while (!armor.error()) { + if (armor.eof() && armor.multiple()) { + armor.restart(); + } + if (armor.eof()) { + break; + } + int ptag = stream_pkt_type(armor.src()); + if (ptag != PGP_PKT_SIGNATURE) { + RNP_LOG("wrong signature tag: %d", ptag); + sigs.clear(); + return RNP_ERROR_BAD_FORMAT; + } + + sigs.emplace_back(); + rnp_result_t ret = sigs.back().parse(armor.src()); + if (ret) { + sigs.clear(); + return ret; + } + } + if (armor.error()) { + sigs.clear(); + return RNP_ERROR_READ; + } + return RNP_SUCCESS; +} + +pgp_sig_subpkt_t::pgp_sig_subpkt_t(const pgp_sig_subpkt_t &src) +{ + type = src.type; + len = src.len; + data = (uint8_t *) malloc(len); + if (!data) { + throw std::bad_alloc(); + } + memcpy(data, src.data, len); + critical = src.critical; + hashed = src.hashed; + parsed = false; + parse(); +} + +pgp_sig_subpkt_t::pgp_sig_subpkt_t(pgp_sig_subpkt_t &&src) +{ + type = src.type; + len = src.len; + data = src.data; + src.data = NULL; + critical = src.critical; + hashed = src.hashed; + parsed = src.parsed; + memcpy(&fields, &src.fields, sizeof(fields)); + src.fields = {}; +} + +pgp_sig_subpkt_t & +pgp_sig_subpkt_t::operator=(pgp_sig_subpkt_t &&src) +{ + if (&src == this) { + return *this; + } + + if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) { + delete fields.sig; + } + type = src.type; + len = src.len; + free(data); + data = src.data; + src.data = NULL; + critical = src.critical; + hashed = src.hashed; + parsed = src.parsed; + fields = src.fields; + src.fields = {}; + return *this; +} + +pgp_sig_subpkt_t & +pgp_sig_subpkt_t::operator=(const pgp_sig_subpkt_t &src) +{ + if (&src == this) { + return *this; + } + + if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) { + delete fields.sig; + } + type = src.type; + len = src.len; + free(data); + data = (uint8_t *) malloc(len); + if (!data) { + throw std::bad_alloc(); + } + memcpy(data, src.data, len); + critical = src.critical; + hashed = src.hashed; + parsed = false; + fields = {}; + parse(); + return *this; +} + +bool +pgp_sig_subpkt_t::parse() +{ + bool oklen = true; + bool checked = true; + + switch (type) { + case PGP_SIG_SUBPKT_CREATION_TIME: + if (!hashed) { + RNP_LOG("creation time subpacket must be hashed"); + checked = false; + } + if ((oklen = len == 4)) { + fields.create = read_uint32(data); + } + break; + case PGP_SIG_SUBPKT_EXPIRATION_TIME: + case PGP_SIG_SUBPKT_KEY_EXPIRY: + if ((oklen = len == 4)) { + fields.expiry = read_uint32(data); + } + break; + case PGP_SIG_SUBPKT_EXPORT_CERT: + if ((oklen = len == 1)) { + fields.exportable = data[0] != 0; + } + break; + case PGP_SIG_SUBPKT_TRUST: + if ((oklen = len == 2)) { + fields.trust.level = data[0]; + fields.trust.amount = data[1]; + } + break; + case PGP_SIG_SUBPKT_REGEXP: + fields.regexp.str = (const char *) data; + fields.regexp.len = len; + break; + case PGP_SIG_SUBPKT_REVOCABLE: + if ((oklen = len == 1)) { + fields.revocable = data[0] != 0; + } + break; + case PGP_SIG_SUBPKT_PREFERRED_SKA: + case PGP_SIG_SUBPKT_PREFERRED_HASH: + case PGP_SIG_SUBPKT_PREF_COMPRESS: + case PGP_SIG_SUBPKT_PREFERRED_AEAD: + fields.preferred.arr = data; + fields.preferred.len = len; + break; + case PGP_SIG_SUBPKT_REVOCATION_KEY: + if ((oklen = len == 22)) { + fields.revocation_key.klass = data[0]; + fields.revocation_key.pkalg = (pgp_pubkey_alg_t) data[1]; + fields.revocation_key.fp = &data[2]; + } + break; + case PGP_SIG_SUBPKT_ISSUER_KEY_ID: + if ((oklen = len == 8)) { + fields.issuer = data; + } + break; + case PGP_SIG_SUBPKT_NOTATION_DATA: + if ((oklen = len >= 8)) { + memcpy(fields.notation.flags, data, 4); + fields.notation.human = fields.notation.flags[0] & 0x80; + fields.notation.nlen = read_uint16(&data[4]); + fields.notation.vlen = read_uint16(&data[6]); + if (len != 8 + fields.notation.nlen + fields.notation.vlen) { + oklen = false; + } else { + fields.notation.name = data + 8; + fields.notation.value = fields.notation.name + fields.notation.nlen; + } + } + break; + case PGP_SIG_SUBPKT_KEYSERV_PREFS: + if ((oklen = len >= 1)) { + fields.ks_prefs.no_modify = (data[0] & 0x80) != 0; + } + break; + case PGP_SIG_SUBPKT_PREF_KEYSERV: + fields.preferred_ks.uri = (const char *) data; + fields.preferred_ks.len = len; + break; + case PGP_SIG_SUBPKT_PRIMARY_USER_ID: + if ((oklen = len == 1)) { + fields.primary_uid = data[0] != 0; + } + break; + case PGP_SIG_SUBPKT_POLICY_URI: + fields.policy.uri = (const char *) data; + fields.policy.len = len; + break; + case PGP_SIG_SUBPKT_KEY_FLAGS: + if ((oklen = len >= 1)) { + fields.key_flags = data[0]; + } + break; + case PGP_SIG_SUBPKT_SIGNERS_USER_ID: + fields.signer.uid = (const char *) data; + fields.signer.len = len; + break; + case PGP_SIG_SUBPKT_REVOCATION_REASON: + if ((oklen = len >= 1)) { + fields.revocation_reason.code = (pgp_revocation_type_t) data[0]; + fields.revocation_reason.str = (const char *) &data[1]; + fields.revocation_reason.len = len - 1; + } + break; + case PGP_SIG_SUBPKT_FEATURES: + if ((oklen = len >= 1)) { + fields.features = data[0]; + } + break; + case PGP_SIG_SUBPKT_SIGNATURE_TARGET: + if ((oklen = len >= 18)) { + fields.sig_target.pkalg = (pgp_pubkey_alg_t) data[0]; + fields.sig_target.halg = (pgp_hash_alg_t) data[1]; + fields.sig_target.hash = &data[2]; + fields.sig_target.hlen = len - 2; + } + break; + case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE: + try { + /* parse signature */ + pgp_packet_body_t pkt(data, len); + pgp_signature_t sig; + oklen = checked = !sig.parse(pkt); + if (checked) { + fields.sig = new pgp_signature_t(std::move(sig)); + } + break; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + case PGP_SIG_SUBPKT_ISSUER_FPR: + if ((oklen = len >= 21)) { + fields.issuer_fp.version = data[0]; + fields.issuer_fp.fp = &data[1]; + fields.issuer_fp.len = len - 1; + } + break; + case PGP_SIG_SUBPKT_PRIVATE_100: + case PGP_SIG_SUBPKT_PRIVATE_101: + case PGP_SIG_SUBPKT_PRIVATE_102: + case PGP_SIG_SUBPKT_PRIVATE_103: + case PGP_SIG_SUBPKT_PRIVATE_104: + case PGP_SIG_SUBPKT_PRIVATE_105: + case PGP_SIG_SUBPKT_PRIVATE_106: + case PGP_SIG_SUBPKT_PRIVATE_107: + case PGP_SIG_SUBPKT_PRIVATE_108: + case PGP_SIG_SUBPKT_PRIVATE_109: + case PGP_SIG_SUBPKT_PRIVATE_110: + oklen = true; + checked = !critical; + if (!checked) { + RNP_LOG("unknown critical private subpacket %d", (int) type); + } + break; + case PGP_SIG_SUBPKT_RESERVED_1: + case PGP_SIG_SUBPKT_RESERVED_8: + case PGP_SIG_SUBPKT_PLACEHOLDER: + case PGP_SIG_SUBPKT_RESERVED_13: + case PGP_SIG_SUBPKT_RESERVED_14: + case PGP_SIG_SUBPKT_RESERVED_15: + case PGP_SIG_SUBPKT_RESERVED_17: + case PGP_SIG_SUBPKT_RESERVED_18: + case PGP_SIG_SUBPKT_RESERVED_19: + /* do not report reserved/placeholder subpacket */ + return !critical; + default: + RNP_LOG("unknown subpacket : %d", (int) type); + return !critical; + } + + if (!oklen) { + RNP_LOG("wrong len %d of subpacket type %d", (int) len, (int) type); + } else { + parsed = 1; + } + return oklen && checked; +} + +pgp_sig_subpkt_t::~pgp_sig_subpkt_t() +{ + if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) { + delete fields.sig; + } + free(data); +} + +pgp_signature_t::pgp_signature_t(const pgp_signature_t &src) +{ + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + + hashed_len = src.hashed_len; + hashed_data = NULL; + if (src.hashed_data) { + if (!(hashed_data = (uint8_t *) malloc(hashed_len))) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material_len = src.material_len; + material_buf = NULL; + if (src.material_buf) { + if (!(material_buf = (uint8_t *) malloc(material_len))) { + throw std::bad_alloc(); + } + memcpy(material_buf, src.material_buf, material_len); + } + subpkts = src.subpkts; +} + +pgp_signature_t::pgp_signature_t(pgp_signature_t &&src) +{ + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + hashed_len = src.hashed_len; + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material_len = src.material_len; + material_buf = src.material_buf; + src.material_buf = NULL; + subpkts = std::move(src.subpkts); +} + +pgp_signature_t & +pgp_signature_t::operator=(pgp_signature_t &&src) +{ + if (this == &src) { + return *this; + } + + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material_len = src.material_len; + free(material_buf); + material_buf = src.material_buf; + src.material_buf = NULL; + subpkts = std::move(src.subpkts); + + return *this; +} + +pgp_signature_t & +pgp_signature_t::operator=(const pgp_signature_t &src) +{ + if (this == &src) { + return *this; + } + + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = NULL; + if (src.hashed_data) { + if (!(hashed_data = (uint8_t *) malloc(hashed_len))) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material_len = src.material_len; + free(material_buf); + material_buf = NULL; + if (src.material_buf) { + if (!(material_buf = (uint8_t *) malloc(material_len))) { + throw std::bad_alloc(); + } + memcpy(material_buf, src.material_buf, material_len); + } + subpkts = src.subpkts; + + return *this; +} + +bool +pgp_signature_t::operator==(const pgp_signature_t &src) const +{ + if ((lbits[0] != src.lbits[0]) || (lbits[1] != src.lbits[1])) { + return false; + } + if ((hashed_len != src.hashed_len) || memcmp(hashed_data, src.hashed_data, hashed_len)) { + return false; + } + return (material_len == src.material_len) && + !memcmp(material_buf, src.material_buf, material_len); +} + +bool +pgp_signature_t::operator!=(const pgp_signature_t &src) const +{ + return !(*this == src); +} + +pgp_signature_t::~pgp_signature_t() +{ + free(hashed_data); + free(material_buf); +} + +pgp_sig_id_t +pgp_signature_t::get_id() const +{ + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + hash->add(hashed_data, hashed_len); + hash->add(material_buf, material_len); + pgp_sig_id_t res = {0}; + static_assert(std::tuple_size<decltype(res)>::value == PGP_SHA1_HASH_SIZE, + "pgp_sig_id_t size mismatch"); + hash->finish(res.data()); + return res; +} + +pgp_sig_subpkt_t * +pgp_signature_t::get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) +{ + if (version < PGP_V4) { + return NULL; + } + for (auto &subpkt : subpkts) { + /* if hashed is false then accept any hashed/not hashed subpacket */ + if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) { + return &subpkt; + } + } + return NULL; +} + +const pgp_sig_subpkt_t * +pgp_signature_t::get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) const +{ + if (version < PGP_V4) { + return NULL; + } + for (auto &subpkt : subpkts) { + /* if hashed is false then accept any hashed/not hashed subpacket */ + if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) { + return &subpkt; + } + } + return NULL; +} + +bool +pgp_signature_t::has_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) const +{ + if (version < PGP_V4) { + return false; + } + for (auto &subpkt : subpkts) { + /* if hashed is false then accept any hashed/not hashed subpacket */ + if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) { + return true; + } + } + return false; +} + +bool +pgp_signature_t::has_keyid() const +{ + return (version < PGP_V4) || has_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false) || + has_keyfp(); +} + +pgp_key_id_t +pgp_signature_t::keyid() const noexcept +{ + /* version 3 uses signature field */ + if (version < PGP_V4) { + return signer; + } + + /* version 4 and up use subpackets */ + pgp_key_id_t res{}; + static_assert(std::tuple_size<decltype(res)>::value == PGP_KEY_ID_SIZE, + "pgp_key_id_t size mismatch"); + + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false); + if (subpkt) { + memcpy(res.data(), subpkt->fields.issuer, PGP_KEY_ID_SIZE); + return res; + } + if ((subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR))) { + memcpy(res.data(), + subpkt->fields.issuer_fp.fp + subpkt->fields.issuer_fp.len - PGP_KEY_ID_SIZE, + PGP_KEY_ID_SIZE); + return res; + } + return res; +} + +void +pgp_signature_t::set_keyid(const pgp_key_id_t &id) +{ + if (version < PGP_V4) { + signer = id; + return; + } + + static_assert(std::tuple_size<std::remove_reference<decltype(id)>::type>::value == + PGP_KEY_ID_SIZE, + "pgp_key_id_t size mismatch"); + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, PGP_KEY_ID_SIZE, true); + subpkt.parsed = true; + subpkt.hashed = false; + memcpy(subpkt.data, id.data(), PGP_KEY_ID_SIZE); + subpkt.fields.issuer = subpkt.data; +} + +bool +pgp_signature_t::has_keyfp() const +{ + if (version < PGP_V4) { + return false; + } + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR); + return subpkt && (subpkt->fields.issuer_fp.len <= PGP_FINGERPRINT_SIZE); +} + +pgp_fingerprint_t +pgp_signature_t::keyfp() const noexcept +{ + pgp_fingerprint_t res{}; + if (version < PGP_V4) { + return res; + } + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR); + if (!subpkt || (subpkt->fields.issuer_fp.len > sizeof(res.fingerprint))) { + return res; + } + res.length = subpkt->fields.issuer_fp.len; + memcpy(res.fingerprint, subpkt->fields.issuer_fp.fp, subpkt->fields.issuer_fp.len); + return res; +} + +void +pgp_signature_t::set_keyfp(const pgp_fingerprint_t &fp) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR, 1 + fp.length, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = 4; + memcpy(subpkt.data + 1, fp.fingerprint, fp.length); + subpkt.fields.issuer_fp.len = fp.length; + subpkt.fields.issuer_fp.version = subpkt.data[0]; + subpkt.fields.issuer_fp.fp = subpkt.data + 1; +} + +uint32_t +pgp_signature_t::creation() const +{ + if (version < PGP_V4) { + return creation_time; + } + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_CREATION_TIME); + return subpkt ? subpkt->fields.create : 0; +} + +void +pgp_signature_t::set_creation(uint32_t ctime) +{ + if (version < PGP_V4) { + creation_time = ctime; + return; + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_CREATION_TIME, 4, true); + subpkt.parsed = true; + subpkt.hashed = true; + STORE32BE(subpkt.data, ctime); + subpkt.fields.create = ctime; +} + +uint32_t +pgp_signature_t::expiration() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_EXPIRATION_TIME); + return subpkt ? subpkt->fields.expiry : 0; +} + +void +pgp_signature_t::set_expiration(uint32_t etime) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_EXPIRATION_TIME, 4, true); + subpkt.parsed = true; + subpkt.hashed = true; + STORE32BE(subpkt.data, etime); + subpkt.fields.expiry = etime; +} + +uint32_t +pgp_signature_t::key_expiration() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY); + return subpkt ? subpkt->fields.expiry : 0; +} + +void +pgp_signature_t::set_key_expiration(uint32_t etime) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY, 4, true); + subpkt.parsed = true; + subpkt.hashed = true; + STORE32BE(subpkt.data, etime); + subpkt.fields.expiry = etime; +} + +uint8_t +pgp_signature_t::key_flags() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS); + return subpkt ? subpkt->fields.key_flags : 0; +} + +void +pgp_signature_t::set_key_flags(uint8_t flags) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = flags; + subpkt.fields.key_flags = flags; +} + +bool +pgp_signature_t::primary_uid() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PRIMARY_USER_ID); + return subpkt ? subpkt->fields.primary_uid : false; +} + +void +pgp_signature_t::set_primary_uid(bool primary) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_PRIMARY_USER_ID, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = primary; + subpkt.fields.primary_uid = primary; +} + +std::vector<uint8_t> +pgp_signature_t::preferred(pgp_sig_subpacket_type_t type) const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(type); + return subpkt ? std::vector<uint8_t>(subpkt->fields.preferred.arr, + subpkt->fields.preferred.arr + + subpkt->fields.preferred.len) : + std::vector<uint8_t>(); +} + +void +pgp_signature_t::set_preferred(const std::vector<uint8_t> &data, pgp_sig_subpacket_type_t type) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + if (data.empty()) { + pgp_sig_subpkt_t *subpkt = get_subpkt(type); + if (subpkt) { + remove_subpkt(subpkt); + } + return; + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(type, data.size(), true); + subpkt.parsed = true; + subpkt.hashed = true; + memcpy(subpkt.data, data.data(), data.size()); + subpkt.fields.preferred.arr = subpkt.data; + subpkt.fields.preferred.len = data.size(); +} + +std::vector<uint8_t> +pgp_signature_t::preferred_symm_algs() const +{ + return preferred(PGP_SIG_SUBPKT_PREFERRED_SKA); +} + +void +pgp_signature_t::set_preferred_symm_algs(const std::vector<uint8_t> &algs) +{ + set_preferred(algs, PGP_SIG_SUBPKT_PREFERRED_SKA); +} + +std::vector<uint8_t> +pgp_signature_t::preferred_hash_algs() const +{ + return preferred(PGP_SIG_SUBPKT_PREFERRED_HASH); +} + +void +pgp_signature_t::set_preferred_hash_algs(const std::vector<uint8_t> &algs) +{ + set_preferred(algs, PGP_SIG_SUBPKT_PREFERRED_HASH); +} + +std::vector<uint8_t> +pgp_signature_t::preferred_z_algs() const +{ + return preferred(PGP_SIG_SUBPKT_PREF_COMPRESS); +} + +void +pgp_signature_t::set_preferred_z_algs(const std::vector<uint8_t> &algs) +{ + set_preferred(algs, PGP_SIG_SUBPKT_PREF_COMPRESS); +} + +uint8_t +pgp_signature_t::key_server_prefs() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEYSERV_PREFS); + return subpkt ? subpkt->data[0] : 0; +} + +void +pgp_signature_t::set_key_server_prefs(uint8_t prefs) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEYSERV_PREFS, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = prefs; + subpkt.fields.ks_prefs.no_modify = prefs & 0x80; +} + +std::string +pgp_signature_t::key_server() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV); + return subpkt ? std::string((char *) subpkt->data, subpkt->len) : ""; +} + +void +pgp_signature_t::set_key_server(const std::string &uri) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + if (uri.empty()) { + pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV); + if (subpkt) { + remove_subpkt(subpkt); + } + return; + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV, uri.size(), true); + subpkt.parsed = true; + subpkt.hashed = true; + memcpy(subpkt.data, uri.data(), uri.size()); + subpkt.fields.preferred_ks.uri = (char *) subpkt.data; + subpkt.fields.preferred_ks.len = uri.size(); +} + +uint8_t +pgp_signature_t::trust_level() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_TRUST); + return subpkt ? subpkt->fields.trust.level : 0; +} + +uint8_t +pgp_signature_t::trust_amount() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_TRUST); + return subpkt ? subpkt->fields.trust.amount : 0; +} + +void +pgp_signature_t::set_trust(uint8_t level, uint8_t amount) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_TRUST, 2, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = level; + subpkt.data[1] = amount; + subpkt.fields.trust.level = level; + subpkt.fields.trust.amount = amount; +} + +bool +pgp_signature_t::revocable() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCABLE); + return subpkt ? subpkt->fields.revocable : true; +} + +void +pgp_signature_t::set_revocable(bool status) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_REVOCABLE, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = status; + subpkt.fields.revocable = status; +} + +std::string +pgp_signature_t::revocation_reason() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON); + return subpkt ? std::string(subpkt->fields.revocation_reason.str, + subpkt->fields.revocation_reason.len) : + ""; +} + +pgp_revocation_type_t +pgp_signature_t::revocation_code() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON); + return subpkt ? subpkt->fields.revocation_reason.code : PGP_REVOCATION_NO_REASON; +} + +void +pgp_signature_t::set_revocation_reason(pgp_revocation_type_t code, const std::string &reason) +{ + size_t datalen = 1 + reason.size(); + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON, datalen, true); + subpkt.hashed = true; + subpkt.data[0] = code; + memcpy(subpkt.data + 1, reason.data(), reason.size()); + + if (!subpkt.parse()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } +} + +bool +pgp_signature_t::key_has_features(pgp_key_feature_t flags) const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_FEATURES); + return subpkt ? subpkt->data[0] & flags : false; +} + +void +pgp_signature_t::set_key_features(pgp_key_feature_t flags) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_FEATURES, 1, true); + subpkt.hashed = true; + subpkt.data[0] = flags; + subpkt.fields.features = flags; + subpkt.parsed = true; +} + +std::string +pgp_signature_t::signer_uid() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_SIGNERS_USER_ID); + return subpkt ? std::string(subpkt->fields.signer.uid, subpkt->fields.signer.len) : ""; +} + +void +pgp_signature_t::set_signer_uid(const std::string &uid) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_SIGNERS_USER_ID, uid.size(), true); + subpkt.hashed = true; + memcpy(subpkt.data, uid.data(), uid.size()); + subpkt.fields.signer.uid = (const char *) subpkt.data; + subpkt.fields.signer.len = subpkt.len; + subpkt.parsed = true; +} + +void +pgp_signature_t::add_notation(const std::string & name, + const std::vector<uint8_t> &value, + bool human, + bool critical) +{ + auto nlen = name.size(); + auto vlen = value.size(); + if ((nlen > 0xffff) || (vlen > 0xffff)) { + RNP_LOG("wrong length"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + auto &subpkt = add_subpkt(PGP_SIG_SUBPKT_NOTATION_DATA, 8 + nlen + vlen, false); + subpkt.hashed = true; + subpkt.critical = critical; + if (human) { + subpkt.data[0] = 0x80; + } + write_uint16(subpkt.data + 4, nlen); + write_uint16(subpkt.data + 6, vlen); + memcpy(subpkt.data + 8, name.data(), nlen); + memcpy(subpkt.data + 8 + nlen, value.data(), vlen); + if (!subpkt.parse()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } +} + +void +pgp_signature_t::add_notation(const std::string &name, const std::string &value, bool critical) +{ + add_notation(name, std::vector<uint8_t>(value.begin(), value.end()), true, critical); +} + +void +pgp_signature_t::set_embedded_sig(const pgp_signature_t &esig) +{ + pgp_rawpacket_t esigpkt(esig); + rnp::MemorySource mem(esigpkt.raw); + size_t len = 0; + stream_read_pkt_len(&mem.src(), &len); + if (!len || (len > 0xffff) || (len >= esigpkt.raw.size())) { + RNP_LOG("wrong pkt len"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE, len, true); + subpkt.hashed = false; + size_t skip = esigpkt.raw.size() - len; + memcpy(subpkt.data, esigpkt.raw.data() + skip, len); + subpkt.fields.sig = new pgp_signature_t(esig); + subpkt.parsed = true; +} + +pgp_sig_subpkt_t & +pgp_signature_t::add_subpkt(pgp_sig_subpacket_type_t type, size_t datalen, bool reuse) +{ + if (version < PGP_V4) { + RNP_LOG("wrong signature version"); + throw std::invalid_argument("version"); + } + + uint8_t *newdata = (uint8_t *) calloc(1, datalen); + if (!newdata) { + RNP_LOG("Allocation failed"); + throw std::bad_alloc(); + } + + pgp_sig_subpkt_t *subpkt = NULL; + if (reuse && (subpkt = get_subpkt(type))) { + *subpkt = {}; + } else { + subpkts.push_back({}); + subpkt = &subpkts.back(); + } + + subpkt->data = newdata; + subpkt->type = type; + subpkt->len = datalen; + return *subpkt; +} + +void +pgp_signature_t::remove_subpkt(pgp_sig_subpkt_t *subpkt) +{ + for (auto it = subpkts.begin(); it < subpkts.end(); it++) { + if (&*it == subpkt) { + subpkts.erase(it); + return; + } + } +} + +bool +pgp_signature_t::matches_onepass(const pgp_one_pass_sig_t &onepass) const +{ + if (!has_keyid()) { + return false; + } + return (halg == onepass.halg) && (palg == onepass.palg) && (type_ == onepass.type) && + (onepass.keyid == keyid()); +} + +rnp_result_t +pgp_signature_t::parse_v3(pgp_packet_body_t &pkt) +{ + /* parse v3-specific fields, not the whole signature */ + uint8_t buf[16] = {}; + if (!pkt.get(buf, 16)) { + RNP_LOG("cannot get enough bytes"); + return RNP_ERROR_BAD_FORMAT; + } + /* length of hashed data, 5 */ + if (buf[0] != 5) { + RNP_LOG("wrong length of hashed data"); + return RNP_ERROR_BAD_FORMAT; + } + /* hashed data */ + free(hashed_data); + if (!(hashed_data = (uint8_t *) malloc(5))) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(hashed_data, &buf[1], 5); + hashed_len = 5; + /* signature type */ + type_ = (pgp_sig_type_t) buf[1]; + /* creation time */ + creation_time = read_uint32(&buf[2]); + /* signer's key id */ + static_assert(std::tuple_size<decltype(signer)>::value == PGP_KEY_ID_SIZE, + "v3 signer field size mismatch"); + memcpy(signer.data(), &buf[6], PGP_KEY_ID_SIZE); + /* public key algorithm */ + palg = (pgp_pubkey_alg_t) buf[14]; + /* hash algorithm */ + halg = (pgp_hash_alg_t) buf[15]; + return RNP_SUCCESS; +} + +#define MAX_SUBPACKETS 64 + +bool +pgp_signature_t::parse_subpackets(uint8_t *buf, size_t len, bool hashed) +{ + bool res = true; + + while (len > 0) { + if (subpkts.size() >= MAX_SUBPACKETS) { + RNP_LOG("too many signature subpackets"); + return false; + } + if (len < 2) { + RNP_LOG("got single byte %d", (int) *buf); + return false; + } + + /* subpacket length */ + size_t splen; + if (*buf < 192) { + splen = *buf; + buf++; + len--; + } else if (*buf < 255) { + splen = ((buf[0] - 192) << 8) + buf[1] + 192; + buf += 2; + len -= 2; + } else { + if (len < 5) { + RNP_LOG("got 4-byte len but only %d bytes in buffer", (int) len); + return false; + } + splen = read_uint32(&buf[1]); + buf += 5; + len -= 5; + } + + if (splen < 1) { + RNP_LOG("got subpacket with 0 length"); + return false; + } + + /* subpacket data */ + if (len < splen) { + RNP_LOG("got subpacket len %d, while only %d bytes left", (int) splen, (int) len); + return false; + } + + pgp_sig_subpkt_t subpkt; + if (!(subpkt.data = (uint8_t *) malloc(splen - 1))) { + RNP_LOG("subpacket data allocation failed"); + return false; + } + + subpkt.type = (pgp_sig_subpacket_type_t)(*buf & 0x7f); + subpkt.critical = !!(*buf & 0x80); + subpkt.hashed = hashed; + subpkt.parsed = 0; + memcpy(subpkt.data, buf + 1, splen - 1); + subpkt.len = splen - 1; + + res = res && subpkt.parse(); + subpkts.push_back(std::move(subpkt)); + len -= splen; + buf += splen; + } + return res; +} + +rnp_result_t +pgp_signature_t::parse_v4(pgp_packet_body_t &pkt) +{ + /* parse v4-specific fields, not the whole signature */ + uint8_t buf[5]; + if (!pkt.get(buf, 5)) { + RNP_LOG("cannot get first 5 bytes"); + return RNP_ERROR_BAD_FORMAT; + } + + /* signature type */ + type_ = (pgp_sig_type_t) buf[0]; + /* public key algorithm */ + palg = (pgp_pubkey_alg_t) buf[1]; + /* hash algorithm */ + halg = (pgp_hash_alg_t) buf[2]; + /* hashed subpackets length */ + uint16_t splen = read_uint16(&buf[3]); + /* hashed subpackets length + 2 bytes of length of unhashed subpackets */ + if (pkt.left() < (size_t)(splen + 2)) { + RNP_LOG("wrong packet or hashed subpackets length"); + return RNP_ERROR_BAD_FORMAT; + } + /* building hashed data */ + free(hashed_data); + if (!(hashed_data = (uint8_t *) malloc(splen + 6))) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + hashed_data[0] = version; + memcpy(hashed_data + 1, buf, 5); + + if (!pkt.get(hashed_data + 6, splen)) { + RNP_LOG("cannot get hashed subpackets data"); + return RNP_ERROR_BAD_FORMAT; + } + hashed_len = splen + 6; + /* parsing hashed subpackets */ + if (!parse_subpackets(hashed_data + 6, splen, true)) { + RNP_LOG("failed to parse hashed subpackets"); + return RNP_ERROR_BAD_FORMAT; + } + /* reading unhashed subpackets */ + if (!pkt.get(splen)) { + RNP_LOG("cannot get unhashed len"); + return RNP_ERROR_BAD_FORMAT; + } + if (pkt.left() < splen) { + RNP_LOG("not enough data for unhashed subpackets"); + return RNP_ERROR_BAD_FORMAT; + } + std::vector<uint8_t> spbuf(splen); + if (!pkt.get(spbuf.data(), splen)) { + RNP_LOG("read of unhashed subpackets failed"); + return RNP_ERROR_READ; + } + if (!parse_subpackets(spbuf.data(), splen, false)) { + RNP_LOG("failed to parse unhashed subpackets"); + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +rnp_result_t +pgp_signature_t::parse(pgp_packet_body_t &pkt) +{ + uint8_t ver = 0; + if (!pkt.get(ver)) { + return RNP_ERROR_BAD_FORMAT; + } + version = (pgp_version_t) ver; + + /* v3 or v4 signature body */ + rnp_result_t res; + if ((ver == PGP_V2) || (ver == PGP_V3)) { + res = parse_v3(pkt); + } else if (ver == PGP_V4) { + res = parse_v4(pkt); + } else { + RNP_LOG("unknown signature version: %d", (int) ver); + res = RNP_ERROR_BAD_FORMAT; + } + + if (res) { + return res; + } + + /* left 16 bits of the hash */ + if (!pkt.get(lbits, 2)) { + RNP_LOG("not enough data for hash left bits"); + return RNP_ERROR_BAD_FORMAT; + } + /* raw signature material */ + material_len = pkt.left(); + if (!material_len) { + RNP_LOG("No signature material"); + return RNP_ERROR_BAD_FORMAT; + } + material_buf = (uint8_t *) malloc(material_len); + if (!material_buf) { + RNP_LOG("Allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + /* we cannot fail here */ + pkt.get(material_buf, material_len); + /* check whether it can be parsed */ + pgp_signature_material_t material = {}; + if (!parse_material(material)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +rnp_result_t +pgp_signature_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_SIGNATURE); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + return parse(pkt); +} + +bool +pgp_signature_t::parse_material(pgp_signature_material_t &material) const +{ + pgp_packet_body_t pkt(material_buf, material_len); + + switch (palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_SIGN_ONLY: + if (!pkt.get(material.rsa.s)) { + return false; + } + break; + case PGP_PKA_DSA: + if (!pkt.get(material.dsa.r) || !pkt.get(material.dsa.s)) { + return false; + } + break; + case PGP_PKA_EDDSA: + if (version < PGP_V4) { + RNP_LOG("Warning! v3 EdDSA signature."); + } + [[fallthrough]]; + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + if (!pkt.get(material.ecc.r) || !pkt.get(material.ecc.s)) { + return false; + } + break; + case PGP_PKA_ELGAMAL: /* we support reading it but will not validate */ + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!pkt.get(material.eg.r) || !pkt.get(material.eg.s)) { + return false; + } + break; + default: + RNP_LOG("Unknown pk algorithm : %d", (int) palg); + return false; + } + + if (pkt.left()) { + RNP_LOG("extra %d bytes in signature packet", (int) pkt.left()); + return false; + } + return true; +} + +void +pgp_signature_t::write(pgp_dest_t &dst) const +{ + if ((version < PGP_V2) || (version > PGP_V4)) { + RNP_LOG("don't know version %d", (int) version); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t pktbody(PGP_PKT_SIGNATURE); + + if (version < PGP_V4) { + /* for v3 signatures hashed data includes only type + creation_time */ + pktbody.add_byte(version); + pktbody.add_byte(hashed_len); + pktbody.add(hashed_data, hashed_len); + pktbody.add(signer); + pktbody.add_byte(palg); + pktbody.add_byte(halg); + } else { + /* for v4 sig->hashed_data must contain most of signature fields */ + pktbody.add(hashed_data, hashed_len); + pktbody.add_subpackets(*this, false); + } + pktbody.add(lbits, 2); + /* write mpis */ + pktbody.add(material_buf, material_len); + pktbody.write(dst); +} + +void +pgp_signature_t::write_material(const pgp_signature_material_t &material) +{ + pgp_packet_body_t pktbody(PGP_PKT_SIGNATURE); + switch (palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_SIGN_ONLY: + pktbody.add(material.rsa.s); + break; + case PGP_PKA_DSA: + pktbody.add(material.dsa.r); + pktbody.add(material.dsa.s); + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + pktbody.add(material.ecc.r); + pktbody.add(material.ecc.s); + break; + case PGP_PKA_ELGAMAL: /* we support writing it but will not generate */ + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + pktbody.add(material.eg.r); + pktbody.add(material.eg.s); + break; + default: + RNP_LOG("Unknown pk algorithm : %d", (int) palg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + free(material_buf); + material_buf = (uint8_t *) malloc(pktbody.size()); + if (!material_buf) { + RNP_LOG("allocation failed"); + throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + memcpy(material_buf, pktbody.data(), pktbody.size()); + material_len = pktbody.size(); +} + +void +pgp_signature_t::fill_hashed_data() +{ + /* we don't have a need to write v2-v3 signatures */ + if ((version < PGP_V2) || (version > PGP_V4)) { + RNP_LOG("don't know version %d", (int) version); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + pgp_packet_body_t hbody(PGP_PKT_RESERVED); + if (version < PGP_V4) { + hbody.add_byte(type()); + hbody.add_uint32(creation_time); + } else { + hbody.add_byte(version); + hbody.add_byte(type()); + hbody.add_byte(palg); + hbody.add_byte(halg); + hbody.add_subpackets(*this, true); + } + + free(hashed_data); + hashed_data = (uint8_t *) malloc(hbody.size()); + if (!hashed_data) { + RNP_LOG("allocation failed"); + throw std::bad_alloc(); + } + memcpy(hashed_data, hbody.data(), hbody.size()); + hashed_len = hbody.size(); +} + +void +rnp_selfsig_cert_info_t::populate(pgp_userid_pkt_t &uid, pgp_signature_t &sig) +{ + /* populate signature */ + sig.set_type(PGP_CERT_POSITIVE); + if (key_expiration) { + sig.set_key_expiration(key_expiration); + } + if (key_flags) { + sig.set_key_flags(key_flags); + } + if (primary) { + sig.set_primary_uid(true); + } + if (!prefs.symm_algs.empty()) { + sig.set_preferred_symm_algs(prefs.symm_algs); + } + if (!prefs.hash_algs.empty()) { + sig.set_preferred_hash_algs(prefs.hash_algs); + } + if (!prefs.z_algs.empty()) { + sig.set_preferred_z_algs(prefs.z_algs); + } + if (!prefs.ks_prefs.empty()) { + sig.set_key_server_prefs(prefs.ks_prefs[0]); + } + if (!prefs.key_server.empty()) { + sig.set_key_server(prefs.key_server); + } + /* populate uid */ + uid.tag = PGP_PKT_USER_ID; + uid.uid_len = userid.size(); + if (!(uid.uid = (uint8_t *) malloc(uid.uid_len))) { + RNP_LOG("alloc failed"); + throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + memcpy(uid.uid, userid.data(), uid.uid_len); +} diff --git a/src/librepgp/stream-sig.h b/src/librepgp/stream-sig.h new file mode 100644 index 0000000..4f36c38 --- /dev/null +++ b/src/librepgp/stream-sig.h @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_SIG_H_ +#define STREAM_SIG_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "rnp.h" +#include "stream-common.h" +#include "stream-packet.h" + +typedef struct pgp_signature_t { + private: + pgp_sig_type_t type_; + std::vector<uint8_t> preferred(pgp_sig_subpacket_type_t type) const; + void set_preferred(const std::vector<uint8_t> &data, pgp_sig_subpacket_type_t type); + rnp_result_t parse_v3(pgp_packet_body_t &pkt); + rnp_result_t parse_v4(pgp_packet_body_t &pkt); + bool parse_subpackets(uint8_t *buf, size_t len, bool hashed); + + public: + pgp_version_t version; + /* common v3 and v4 fields */ + pgp_pubkey_alg_t palg; + pgp_hash_alg_t halg; + uint8_t lbits[2]; + uint8_t * hashed_data; + size_t hashed_len; + uint8_t * material_buf; /* raw signature material */ + size_t material_len; /* raw signature material length */ + + /* v3 - only fields */ + uint32_t creation_time; + pgp_key_id_t signer; + + /* v4 - only fields */ + std::vector<pgp_sig_subpkt_t> subpkts; + + pgp_signature_t() + : type_(PGP_SIG_BINARY), version(PGP_VUNKNOWN), palg(PGP_PKA_NOTHING), + halg(PGP_HASH_UNKNOWN), hashed_data(NULL), hashed_len(0), material_buf(NULL), + material_len(0), creation_time(0){}; + pgp_signature_t(const pgp_signature_t &src); + pgp_signature_t(pgp_signature_t &&src); + pgp_signature_t &operator=(pgp_signature_t &&src); + pgp_signature_t &operator=(const pgp_signature_t &src); + bool operator==(const pgp_signature_t &src) const; + bool operator!=(const pgp_signature_t &src) const; + ~pgp_signature_t(); + + /* @brief Get signature's type */ + pgp_sig_type_t + type() const + { + return type_; + }; + void + set_type(pgp_sig_type_t atype) + { + type_ = atype; + }; + + bool + is_document() const + { + return (type_ == PGP_SIG_BINARY) || (type_ == PGP_SIG_TEXT); + }; + + /** @brief Calculate the unique signature identifier by hashing signature's fields. */ + pgp_sig_id_t get_id() const; + + /** + * @brief Get v4 signature's subpacket of the specified type and hashedness. + * @param stype subpacket type. + * @param hashed If true (default), then will search for subpacket only in hashed (i.e. + * covered by signature) area, otherwise will search in both hashed and non-hashed areas. + * @return pointer to the subpacket, or NULL if subpacket was not found. + */ + pgp_sig_subpkt_t * get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed = true); + const pgp_sig_subpkt_t *get_subpkt(pgp_sig_subpacket_type_t stype, + bool hashed = true) const; + /* @brief Check whether v4 signature has subpacket of the specified type/hashedness */ + bool has_subpkt(pgp_sig_subpacket_type_t stype, bool hashed = true) const; + /* @brief Check whether signature has signing key id (via v3 field, or v4 key id/key fp + * subpacket) */ + bool has_keyid() const; + /** + * @brief Get signer's key id if available. Availability may be checked via has_keyid(). + * @return signer's key id if available, or empty (zero-filled) keyid otherwise. + */ + pgp_key_id_t keyid() const noexcept; + /** @brief Set the signer's key id for the signature being populated. Version should be set + * prior of setting key id. */ + void set_keyid(const pgp_key_id_t &id); + /** + * @brief Check whether signature has valid issuer fingerprint subpacket. + * @return true if there is one, and it can be safely returned via keyfp() method or false + * otherwise. + */ + bool has_keyfp() const; + /** + * @brief Get signing key's fingerprint if it is available. Availability may be checked via + * has_keyfp() method. + * @return fingerprint (or empty zero-size fp in case it is unavailable) + */ + pgp_fingerprint_t keyfp() const noexcept; + + /** @brief Set signing key's fingerprint. Works only for signatures with version 4 and up, + * so version should be set prior to fingerprint. */ + void set_keyfp(const pgp_fingerprint_t &fp); + + /** + * @brief Get signature's creation time + * @return time in seconds since the Jan 1, 1970 UTC. 0 is the default value and returned + * even if creation time is not available + */ + uint32_t creation() const; + + /** + * @brief Set signature's creation time + * @param ctime creation time in seconds since the Jan 1, 1970 UTC. + */ + void set_creation(uint32_t ctime); + + /** + * @brief Get the signature's expiration time + * @return expiration time in seconds since the creation time. 0 if signature never + * expires. + */ + uint32_t expiration() const; + + /** + * @brief Set the signature's expiration time + * @param etime expiration time + */ + void set_expiration(uint32_t etime); + + /** + * @brief Get the key expiration time + * @return expiration time in seconds since the creation time. 0 if key never expires. + */ + uint32_t key_expiration() const; + + /** + * @brief Set the key expiration time + * @param etime expiration time + */ + void set_key_expiration(uint32_t etime); + + /** + * @brief Get the key flags + * @return byte of key flags. If there is no corresponding subpackets then 0 is returned. + */ + uint8_t key_flags() const; + + /** + * @brief Set the key flags + * @param flags byte of key flags + */ + void set_key_flags(uint8_t flags); + + /** + * @brief Get the primary user id flag + * @return true if user id is marked as primary or false otherwise + */ + bool primary_uid() const; + + /** + * @brief Set the primary user id flag + * @param primary true if user id should be marked as primary + */ + void set_primary_uid(bool primary); + + /** @brief Get preferred symmetric algorithms if any. If there are no ones then empty + * vector is returned. */ + std::vector<uint8_t> preferred_symm_algs() const; + + /** @brief Set the preferred symmetric algorithms. If empty vector is passed then + * corresponding subpacket is deleted. */ + void set_preferred_symm_algs(const std::vector<uint8_t> &algs); + + /** @brief Get preferred hash algorithms if any. If there are no ones then empty vector is + * returned.*/ + std::vector<uint8_t> preferred_hash_algs() const; + + /** @brief Set the preferred hash algorithms. If empty vector is passed then corresponding + * subpacket is deleted. */ + void set_preferred_hash_algs(const std::vector<uint8_t> &algs); + + /** @brief Get preferred compression algorithms if any. If there are no ones then empty + * vector is returned.*/ + std::vector<uint8_t> preferred_z_algs() const; + + /** @brief Set the preferred compression algorithms. If empty vector is passed then + * corresponding subpacket is deleted. */ + void set_preferred_z_algs(const std::vector<uint8_t> &algs); + + /** @brief Get key server preferences flags. If subpacket is not available then 0 is + * returned. */ + uint8_t key_server_prefs() const; + + /** @brief Set key server preferences flags. */ + void set_key_server_prefs(uint8_t prefs); + + /** @brief Get preferred key server URI, if available. Otherwise empty string is returned. + */ + std::string key_server() const; + + /** @brief Set preferred key server URI. If it is empty string then subpacket is deleted if + * it is available. */ + void set_key_server(const std::string &uri); + + /** @brief Get trust level, if available. Otherwise will return 0. See RFC 4880, 5.2.3.14. + * for the detailed information on trust level and amount. + */ + uint8_t trust_level() const; + + /** @brief Get trust amount, if available. Otherwise will return 0. See RFC 4880, 5.2.3.14. + * for the detailed information on trust level and amount. + */ + uint8_t trust_amount() const; + + /** @brief Set the trust level and amount. See RFC 4880, 5.2.3.14. + * for the detailed information on trust level and amount. + */ + void set_trust(uint8_t level, uint8_t amount); + + /** @brief check whether signature is revocable. True by default. + */ + bool revocable() const; + + /** @brief Set the signature's revocability status. + */ + void set_revocable(bool status); + + /** @brief Get the key/subkey revocation reason in humand-readable form. If there is no + * revocation reason subpacket, then empty string will be returned. + */ + std::string revocation_reason() const; + + /** @brief Get the key/subkey revocation code. If there is no revocation reason subpacket, + * then PGP_REVOCATION_NO_REASON will be rerturned. See the RFC 4880, 5.2.3.24 for + * the detailed explanation. + */ + pgp_revocation_type_t revocation_code() const; + + /** @brief Set the revocation reason and code for key/subkey revocation signature. See the + * RFC 4880, 5.2.3.24 for the detailed explanation. + */ + void set_revocation_reason(pgp_revocation_type_t code, const std::string &reason); + + /** + * @brief Check whether signer's key supports certain feature(s). Makes sense only for + * self-signature, for more details see the RFC 4880bis, 5.2.3.25. If there is + * no corresponding subpacket then false will be returned. + * @param flags one or more flags, combined via bitwise OR operation. + * @return true if key is claimed to support all of the features listed in flags, or false + * otherwise + */ + bool key_has_features(pgp_key_feature_t flags) const; + + /** + * @brief Set the features supported by the signer's key, makes sense only for + * self-signature. For more details see the RFC 4880bis, 5.2.3.25. + * @param flags one or more flags, combined via bitwise OR operation. + */ + void set_key_features(pgp_key_feature_t flags); + + /** @brief Get signer's user id, if available. Otherwise empty string is returned. See the + * RFC 4880bis, 5.2.3.23 for details. + */ + std::string signer_uid() const; + + /** + * @brief Set the signer's uid, responcible for the signature creation. See the RFC + * 4880bis, 5.2.3.23 for details. + */ + void set_signer_uid(const std::string &uid); + + /** + * @brief Add notation. + */ + void add_notation(const std::string & name, + const std::vector<uint8_t> &value, + bool human = true, + bool critical = false); + + /** + * @brief Add human-readable notation. + */ + void add_notation(const std::string &name, + const std::string &value, + bool critical = false); + + /** + * @brief Set the embedded signature. + * @param esig populated and calculated embedded signature. + */ + void set_embedded_sig(const pgp_signature_t &esig); + + /** + * @brief Add subpacket of the specified type to v4 signature + * @param type type of the subpacket + * @param datalen length of the subpacket body + * @param reuse replace already existing subpacket of the specified type if any + * @return reference to the subpacket structure or throws an exception + */ + pgp_sig_subpkt_t &add_subpkt(pgp_sig_subpacket_type_t type, size_t datalen, bool reuse); + + /** + * @brief Remove signature's subpacket + * @param subpkt subpacket to remove. If not in the subpackets list then no action is + * taken. + */ + void remove_subpkt(pgp_sig_subpkt_t *subpkt); + + /** + * @brief Check whether signature packet matches one-pass signature packet. + * @param onepass reference to the read one-pass signature packet + * @return true if sig corresponds to onepass or false otherwise + */ + bool matches_onepass(const pgp_one_pass_sig_t &onepass) const; + + /** + * @brief Parse signature body (i.e. without checking the packet header). + * + * @param pkt packet body with data. + * @return RNP_SUCCESS or error code if failed. May also throw an exception. + */ + rnp_result_t parse(pgp_packet_body_t &pkt); + + /** + * @brief Parse signature packet from source. + * + * @param src source with data. + * @return RNP_SUCCESS or error code if failed. May also throw an exception. + */ + rnp_result_t parse(pgp_source_t &src); + + /** + * @brief Parse signature material, stored in the signature in raw. + * + * @param material on success parsed material will be stored here. + * @return true on success or false otherwise. May also throw an exception. + */ + bool parse_material(pgp_signature_material_t &material) const; + + /** + * @brief Write signature to the destination. May throw an exception. + */ + void write(pgp_dest_t &dst) const; + + /** + * @brief Write the signature material's raw representation. May throw an exception. + * + * @param material populated signature material. + */ + void write_material(const pgp_signature_material_t &material); + + /** + * @brief Fill signature's hashed data. This includes all the fields from signature which + * are hashed after the previous document or key fields. + */ + void fill_hashed_data(); +} pgp_signature_t; + +typedef std::vector<pgp_signature_t> pgp_signature_list_t; + +/* information about the validated signature */ +typedef struct pgp_signature_info_t { + pgp_signature_t *sig{}; /* signature, or NULL if there were parsing error */ + bool valid{}; /* signature is cryptographically valid (but may be expired) */ + bool unknown{}; /* signature is unknown - parsing error, wrong version, etc */ + bool no_signer{}; /* no signer's public key available */ + bool expired{}; /* signature is expired */ + bool signer_valid{}; /* assume that signing key is valid */ + bool ignore_expiry{}; /* ignore signer's key expiration time */ +} pgp_signature_info_t; + +/** + * @brief Hash key packet. Used in signatures and v4 fingerprint calculation. + * Throws exception on error. + * @param key key packet, must be populated + * @param hash initialized hash context + */ +void signature_hash_key(const pgp_key_pkt_t &key, rnp::Hash &hash); + +void signature_hash_userid(const pgp_userid_pkt_t &uid, rnp::Hash &hash, pgp_version_t sigver); + +std::unique_ptr<rnp::Hash> signature_hash_certification(const pgp_signature_t & sig, + const pgp_key_pkt_t & key, + const pgp_userid_pkt_t &userid); + +std::unique_ptr<rnp::Hash> signature_hash_binding(const pgp_signature_t &sig, + const pgp_key_pkt_t & key, + const pgp_key_pkt_t & subkey); + +std::unique_ptr<rnp::Hash> signature_hash_direct(const pgp_signature_t &sig, + const pgp_key_pkt_t & key); + +/** + * @brief Parse stream with signatures to the signatures list. + * Can handle binary or armored stream with signatures, including stream with multiple + * armored signatures. + * + * @param src signatures stream, cannot be NULL. + * @param sigs on success parsed signature structures will be put here. + * @return RNP_SUCCESS or error code otherwise. + */ +rnp_result_t process_pgp_signatures(pgp_source_t &src, pgp_signature_list_t &sigs); + +#endif diff --git a/src/librepgp/stream-write.cpp b/src/librepgp/stream-write.cpp new file mode 100644 index 0000000..60d867a --- /dev/null +++ b/src/librepgp/stream-write.cpp @@ -0,0 +1,1973 @@ +/* + * Copyright (c) 2017-2023, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <sys/param.h> +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#ifdef HAVE_ZLIB_H +#include <zlib.h> +#endif +#ifdef HAVE_BZLIB_H +#include <bzlib.h> +#endif +#include <rnp/rnp_def.h> +#include "stream-def.h" +#include "stream-ctx.h" +#include "stream-write.h" +#include "stream-packet.h" +#include "stream-armor.h" +#include "stream-sig.h" +#include "pgp-key.h" +#include "fingerprint.h" +#include "types.h" +#include "crypto/signatures.h" +#include "defaults.h" +#include <time.h> +#include <algorithm> + +/* 8192 bytes, as GnuPG */ +#define PGP_PARTIAL_PKT_SIZE_BITS (13) +#define PGP_PARTIAL_PKT_BLOCK_SIZE (1 << PGP_PARTIAL_PKT_SIZE_BITS) + +/* common fields for encrypted, compressed and literal data */ +typedef struct pgp_dest_packet_param_t { + pgp_dest_t *writedst; /* destination to write to, could be partial */ + pgp_dest_t *origdst; /* original dest passed to init_*_dst */ + bool partial; /* partial length packet */ + bool indeterminate; /* indeterminate length packet */ + int tag; /* packet tag */ + uint8_t hdr[PGP_MAX_HEADER_SIZE]; /* header, including length, as it was written */ + size_t hdrlen; /* number of bytes in hdr */ +} pgp_dest_packet_param_t; + +typedef struct pgp_dest_compressed_param_t { + pgp_dest_packet_param_t pkt; + pgp_compression_type_t alg; + union { + z_stream z; + bz_stream bz; + }; + bool zstarted; /* whether we initialize zlib/bzip2 */ + uint8_t cache[PGP_INPUT_CACHE_SIZE / 2]; /* pre-allocated cache for compression */ + size_t len; /* number of bytes cached */ +} pgp_dest_compressed_param_t; + +typedef struct pgp_dest_encrypted_param_t { + pgp_dest_packet_param_t pkt; /* underlying packet-related params */ + rnp_ctx_t * ctx; /* rnp operation context with additional parameters */ + rnp::AuthType auth_type; /* Authentication type: MDC, AEAD or none */ + pgp_crypt_t encrypt; /* encrypting crypto */ + std::unique_ptr<rnp::Hash> mdc; /* mdc SHA1 hash */ + pgp_aead_alg_t aalg; /* AEAD algorithm used */ + uint8_t iv[PGP_AEAD_MAX_NONCE_LEN]; /* iv for AEAD mode */ + uint8_t ad[PGP_AEAD_MAX_AD_LEN]; /* additional data for AEAD mode */ + size_t adlen; /* length of additional data, including chunk idx */ + size_t chunklen; /* length of the AEAD chunk in bytes */ + size_t chunkout; /* how many bytes from the chunk were written out */ + size_t chunkidx; /* index of the current AEAD chunk */ + size_t cachelen; /* how many bytes are in cache, for AEAD */ + uint8_t cache[PGP_AEAD_CACHE_LEN]; /* pre-allocated cache for encryption */ +} pgp_dest_encrypted_param_t; + +typedef struct pgp_dest_signer_info_t { + pgp_one_pass_sig_t onepass; + pgp_key_t * key; + pgp_hash_alg_t halg; + int64_t sigcreate; + uint64_t sigexpire; +} pgp_dest_signer_info_t; + +typedef struct pgp_dest_signed_param_t { + pgp_dest_t * writedst; /* destination to write to */ + rnp_ctx_t * ctx; /* rnp operation context with additional parameters */ + pgp_password_provider_t *password_provider; /* password provider from write handler */ + std::vector<pgp_dest_signer_info_t> siginfos; /* list of pgp_dest_signer_info_t */ + rnp::HashList hashes; /* hashes to pass raw data through and then sign */ + bool clr_start; /* we are on the start of the line */ + uint8_t clr_buf[CT_BUF_LEN]; /* buffer to hold partial line data */ + size_t clr_buflen; /* number of bytes in buffer */ + + pgp_dest_signed_param_t() = default; + ~pgp_dest_signed_param_t() = default; +} pgp_dest_signed_param_t; + +typedef struct pgp_dest_partial_param_t { + pgp_dest_t *writedst; + uint8_t part[PGP_PARTIAL_PKT_BLOCK_SIZE]; + uint8_t parthdr; /* header byte for the current part */ + size_t partlen; /* length of the current part, up to PARTIAL_PKT_BLOCK_SIZE */ + size_t len; /* bytes cached in part */ +} pgp_dest_partial_param_t; + +static rnp_result_t +partial_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param; + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (len > param->partlen - param->len) { + /* we have full part - in block and in buf */ + size_t wrlen = param->partlen - param->len; + dst_write(param->writedst, ¶m->parthdr, 1); + dst_write(param->writedst, param->part, param->len); + dst_write(param->writedst, buf, wrlen); + + buf = (uint8_t *) buf + wrlen; + len -= wrlen; + param->len = 0; + + /* writing all full parts directly from buf */ + while (len >= param->partlen) { + dst_write(param->writedst, ¶m->parthdr, 1); + dst_write(param->writedst, buf, param->partlen); + buf = (uint8_t *) buf + param->partlen; + len -= param->partlen; + } + } + + /* caching rest of the buf */ + if (len > 0) { + memcpy(¶m->part[param->len], buf, len); + param->len += len; + } + + return RNP_SUCCESS; +} + +static rnp_result_t +partial_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param; + uint8_t hdr[5]; + int lenlen; + + lenlen = write_packet_len(hdr, param->len); + dst_write(param->writedst, hdr, lenlen); + dst_write(param->writedst, param->part, param->len); + + return param->writedst->werr; +} + +static void +partial_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param; + + if (!param) { + return; + } + + free(param); + dst->param = NULL; +} + +static rnp_result_t +init_partial_pkt_dst(pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_partial_param_t *param; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_partial_param_t *) dst->param; + param->writedst = writedst; + param->partlen = PGP_PARTIAL_PKT_BLOCK_SIZE; + param->parthdr = 0xE0 | PGP_PARTIAL_PKT_SIZE_BITS; + dst->param = param; + dst->write = partial_dst_write; + dst->finish = partial_dst_finish; + dst->close = partial_dst_close; + dst->type = PGP_STREAM_PARLEN_PACKET; + + return RNP_SUCCESS; +} + +/** @brief helper function for streamed packets (literal, encrypted and compressed). + * Allocates part len destination if needed and writes header + **/ +static bool +init_streamed_packet(pgp_dest_packet_param_t *param, pgp_dest_t *dst) +{ + rnp_result_t ret; + + if (param->partial) { + param->hdr[0] = param->tag | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + dst_write(dst, ¶m->hdr, 1); + + if ((param->writedst = (pgp_dest_t *) calloc(1, sizeof(*param->writedst))) == NULL) { + RNP_LOG("part len dest allocation failed"); + return false; + } + ret = init_partial_pkt_dst(param->writedst, dst); + if (ret != RNP_SUCCESS) { + free(param->writedst); + param->writedst = NULL; + return false; + } + param->origdst = dst; + + param->hdr[1] = ((pgp_dest_partial_param_t *) param->writedst->param)->parthdr; + param->hdrlen = 2; + return true; + } + + if (param->indeterminate) { + if (param->tag > 0xf) { + RNP_LOG("indeterminate tag > 0xf"); + } + + param->hdr[0] = ((param->tag & 0xf) << PGP_PTAG_OF_CONTENT_TAG_SHIFT) | + PGP_PTAG_OLD_LEN_INDETERMINATE; + param->hdrlen = 1; + dst_write(dst, ¶m->hdr, 1); + + param->writedst = dst; + param->origdst = dst; + return true; + } + + RNP_LOG("wrong call"); + return false; +} + +static rnp_result_t +finish_streamed_packet(pgp_dest_packet_param_t *param) +{ + if (param->partial) { + return dst_finish(param->writedst); + } + return RNP_SUCCESS; +} + +static void +close_streamed_packet(pgp_dest_packet_param_t *param, bool discard) +{ + if (param->partial) { + dst_close(param->writedst, discard); + free(param->writedst); + param->writedst = NULL; + } +} + +static rnp_result_t +encrypted_dst_write_cfb(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + size_t sz; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (param->auth_type == rnp::AuthType::MDC) { + try { + param->mdc->add(buf, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + } + + while (len > 0) { + sz = len > sizeof(param->cache) ? sizeof(param->cache) : len; + pgp_cipher_cfb_encrypt(¶m->encrypt, param->cache, (const uint8_t *) buf, sz); + dst_write(param->pkt.writedst, param->cache, sz); + len -= sz; + buf = (uint8_t *) buf + sz; + } + + return RNP_SUCCESS; +} + +#if defined(ENABLE_AEAD) +static rnp_result_t +encrypted_start_aead_chunk(pgp_dest_encrypted_param_t *param, size_t idx, bool last) +{ + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen; + size_t taglen; + bool res; + uint64_t total; + + taglen = pgp_cipher_aead_tag_len(param->aalg); + + /* finish the previous chunk if needed*/ + if ((idx > 0) && (param->chunkout + param->cachelen > 0)) { + if (param->cachelen + taglen > sizeof(param->cache)) { + RNP_LOG("wrong state in aead"); + return RNP_ERROR_BAD_STATE; + } + + if (!pgp_cipher_aead_finish( + ¶m->encrypt, param->cache, param->cache, param->cachelen)) { + return RNP_ERROR_BAD_STATE; + } + + dst_write(param->pkt.writedst, param->cache, param->cachelen + taglen); + } + + /* set chunk index for additional data */ + STORE64BE(param->ad + param->adlen - 8, idx); + + if (last) { + if (!(param->chunkout + param->cachelen)) { + /* we need to clearly reset it since cipher was initialized but not finished */ + pgp_cipher_aead_reset(¶m->encrypt); + } + + total = idx * param->chunklen; + if (param->cachelen + param->chunkout) { + if (param->chunklen < (param->cachelen + param->chunkout)) { + RNP_LOG("wrong last chunk state in aead"); + return RNP_ERROR_BAD_STATE; + } + total -= param->chunklen - param->cachelen - param->chunkout; + } + + STORE64BE(param->ad + param->adlen, total); + param->adlen += 8; + } + if (!pgp_cipher_aead_set_ad(¶m->encrypt, param->ad, param->adlen)) { + RNP_LOG("failed to set ad"); + return RNP_ERROR_BAD_STATE; + } + + /* set chunk index for nonce */ + nlen = pgp_cipher_aead_nonce(param->aalg, param->iv, nonce, idx); + + /* start cipher */ + res = pgp_cipher_aead_start(¶m->encrypt, nonce, nlen); + + /* write final authentication tag */ + if (last) { + res = res && pgp_cipher_aead_finish(¶m->encrypt, param->cache, param->cache, 0); + if (res) { + dst_write(param->pkt.writedst, param->cache, taglen); + } + } + + param->chunkidx = idx; + param->chunkout = 0; + + return res ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS; +} +#endif + +static rnp_result_t +encrypted_dst_write_aead(pgp_dest_t *dst, const void *buf, size_t len) +{ +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD is not enabled."); + return RNP_ERROR_WRITE; +#else + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + + size_t sz; + size_t gran; + rnp_result_t res; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!len) { + return RNP_SUCCESS; + } + + /* because of botan's FFI granularity we need to make things a bit complicated */ + gran = pgp_cipher_aead_granularity(¶m->encrypt); + + if (param->cachelen > param->chunklen - param->chunkout) { + RNP_LOG("wrong AEAD cache state"); + return RNP_ERROR_BAD_STATE; + } + + while (len > 0) { + sz = std::min(sizeof(param->cache) - PGP_AEAD_MAX_TAG_LEN - param->cachelen, len); + sz = std::min(sz, param->chunklen - param->chunkout - param->cachelen); + memcpy(param->cache + param->cachelen, buf, sz); + param->cachelen += sz; + + if (param->cachelen == param->chunklen - param->chunkout) { + /* we have the tail of the chunk in cache */ + if ((res = encrypted_start_aead_chunk(param, param->chunkidx + 1, false))) { + return res; + } + param->cachelen = 0; + } else if (param->cachelen >= gran) { + /* we have part of the chunk - so need to adjust it to the granularity */ + size_t gransz = param->cachelen - param->cachelen % gran; + if (!pgp_cipher_aead_update(¶m->encrypt, param->cache, param->cache, gransz)) { + return RNP_ERROR_BAD_STATE; + } + dst_write(param->pkt.writedst, param->cache, gransz); + memmove(param->cache, param->cache + gransz, param->cachelen - gransz); + param->cachelen -= gransz; + param->chunkout += gransz; + } + + len -= sz; + buf = (uint8_t *) buf + sz; + } + + return RNP_SUCCESS; +#endif +} + +static rnp_result_t +encrypted_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + + if (param->auth_type == rnp::AuthType::AEADv1) { +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD is not enabled."); + rnp_result_t res = RNP_ERROR_NOT_IMPLEMENTED; +#else + size_t chunks = param->chunkidx; + /* if we didn't write anything in current chunk then discard it and restart */ + if (param->chunkout || param->cachelen) { + chunks++; + } + + rnp_result_t res = encrypted_start_aead_chunk(param, chunks, true); + pgp_cipher_aead_destroy(¶m->encrypt); +#endif + if (res) { + finish_streamed_packet(¶m->pkt); + return res; + } + } else if (param->auth_type == rnp::AuthType::MDC) { + uint8_t mdcbuf[MDC_V1_SIZE]; + mdcbuf[0] = MDC_PKT_TAG; + mdcbuf[1] = MDC_V1_SIZE - 2; + try { + param->mdc->add(mdcbuf, 2); + param->mdc->finish(&mdcbuf[2]); + param->mdc = nullptr; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + pgp_cipher_cfb_encrypt(¶m->encrypt, mdcbuf, mdcbuf, MDC_V1_SIZE); + dst_write(param->pkt.writedst, mdcbuf, MDC_V1_SIZE); + } + + return finish_streamed_packet(¶m->pkt); +} + +static void +encrypted_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + + if (!param) { + return; + } + + if (param->auth_type == rnp::AuthType::AEADv1) { +#if defined(ENABLE_AEAD) + pgp_cipher_aead_destroy(¶m->encrypt); +#endif + } else { + pgp_cipher_cfb_finish(¶m->encrypt); + } + close_streamed_packet(¶m->pkt, discard); + delete param; + dst->param = NULL; +} + +static rnp_result_t +encrypted_add_recipient(pgp_write_handler_t *handler, + pgp_dest_t * dst, + pgp_key_t * userkey, + const uint8_t * key, + const unsigned keylen) +{ + pgp_pk_sesskey_t pkey; + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + rnp_result_t ret = RNP_ERROR_GENERIC; + + /* Use primary key if good for encryption, otherwise look in subkey list */ + userkey = find_suitable_key(PGP_OP_ENCRYPT, userkey, handler->key_provider); + if (!userkey) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + + /* Fill pkey */ + pkey.version = PGP_PKSK_V3; + pkey.alg = userkey->alg(); + pkey.key_id = userkey->keyid(); + + /* Encrypt the session key */ + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE + 3> enckey; + enckey[0] = param->ctx->ealg; + memcpy(&enckey[1], key, keylen); + + /* Calculate checksum */ + rnp::secure_array<unsigned, 1> checksum; + + for (unsigned i = 1; i <= keylen; i++) { + checksum[0] += enckey[i]; + } + enckey[keylen + 1] = (checksum[0] >> 8) & 0xff; + enckey[keylen + 2] = checksum[0] & 0xff; + + pgp_encrypted_material_t material; + + switch (userkey->alg()) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: { + ret = rsa_encrypt_pkcs1(&handler->ctx->ctx->rng, + &material.rsa, + enckey.data(), + keylen + 3, + &userkey->material().rsa); + if (ret) { + RNP_LOG("rsa_encrypt_pkcs1 failed"); + return ret; + } + break; + } + case PGP_PKA_SM2: { +#if defined(ENABLE_SM2) + ret = sm2_encrypt(&handler->ctx->ctx->rng, + &material.sm2, + enckey.data(), + keylen + 3, + PGP_HASH_SM3, + &userkey->material().ec); + if (ret) { + RNP_LOG("sm2_encrypt failed"); + return ret; + } + break; +#else + RNP_LOG("sm2_encrypt is not available"); + return RNP_ERROR_NOT_IMPLEMENTED; +#endif + } + case PGP_PKA_ECDH: { + if (!curve_supported(userkey->material().ec.curve)) { + RNP_LOG("ECDH encrypt: curve %d is not supported.", + (int) userkey->material().ec.curve); + return RNP_ERROR_NOT_SUPPORTED; + } + ret = ecdh_encrypt_pkcs5(&handler->ctx->ctx->rng, + &material.ecdh, + enckey.data(), + keylen + 3, + &userkey->material().ec, + userkey->fp()); + if (ret) { + RNP_LOG("ECDH encryption failed %d", ret); + return ret; + } + break; + } + case PGP_PKA_ELGAMAL: { + ret = elgamal_encrypt_pkcs1(&handler->ctx->ctx->rng, + &material.eg, + enckey.data(), + keylen + 3, + &userkey->material().eg); + if (ret) { + RNP_LOG("pgp_elgamal_public_encrypt failed"); + return ret; + } + break; + } + default: + RNP_LOG("unsupported alg: %d", (int) userkey->alg()); + return ret; + } + + /* Writing symmetric key encrypted session key packet */ + try { + pkey.write_material(material); + pkey.write(*param->pkt.origdst); + return param->pkt.origdst->werr; + } catch (const std::exception &e) { + return RNP_ERROR_WRITE; + } +} + +#if defined(ENABLE_AEAD) +static bool +encrypted_sesk_set_ad(pgp_crypt_t *crypt, pgp_sk_sesskey_t *skey) +{ + uint8_t ad_data[4]; + + ad_data[0] = PGP_PKT_SK_SESSION_KEY | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + ad_data[1] = skey->version; + ad_data[2] = skey->alg; + ad_data[3] = skey->aalg; + + return pgp_cipher_aead_set_ad(crypt, ad_data, 4); +} +#endif + +static rnp_result_t +encrypted_add_password(rnp_symmetric_pass_info_t * pass, + pgp_dest_encrypted_param_t *param, + uint8_t * key, + const unsigned keylen, + bool singlepass) +{ + pgp_sk_sesskey_t skey = {}; + pgp_crypt_t kcrypt; + + skey.s2k = pass->s2k; + + if (param->auth_type != rnp::AuthType::AEADv1) { + skey.version = PGP_SKSK_V4; + if (singlepass) { + /* if there are no public keys then we do not encrypt session key in the packet */ + skey.alg = param->ctx->ealg; + skey.enckeylen = 0; + memcpy(key, pass->key.data(), keylen); + } else { + /* We may use different algo for CEK and KEK */ + skey.enckeylen = keylen + 1; + skey.enckey[0] = param->ctx->ealg; + memcpy(&skey.enckey[1], key, keylen); + skey.alg = pass->s2k_cipher; + if (!pgp_cipher_cfb_start(&kcrypt, skey.alg, pass->key.data(), NULL)) { + RNP_LOG("key encryption failed"); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_cipher_cfb_encrypt(&kcrypt, skey.enckey, skey.enckey, skey.enckeylen); + pgp_cipher_cfb_finish(&kcrypt); + } + } else { +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD support is not enabled."); + return RNP_ERROR_NOT_IMPLEMENTED; +#else + /* AEAD-encrypted v5 packet */ + if ((param->ctx->aalg != PGP_AEAD_EAX) && (param->ctx->aalg != PGP_AEAD_OCB)) { + RNP_LOG("unsupported AEAD algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } + + skey.version = PGP_SKSK_V5; + skey.alg = pass->s2k_cipher; + skey.aalg = param->ctx->aalg; + skey.ivlen = pgp_cipher_aead_nonce_len(skey.aalg); + skey.enckeylen = keylen + pgp_cipher_aead_tag_len(skey.aalg); + + try { + param->ctx->ctx->rng.get(skey.iv, skey.ivlen); + } catch (const std::exception &e) { + return RNP_ERROR_RNG; + } + + /* initialize cipher */ + if (!pgp_cipher_aead_init(&kcrypt, skey.alg, skey.aalg, pass->key.data(), false)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* set additional data */ + if (!encrypted_sesk_set_ad(&kcrypt, &skey)) { + return RNP_ERROR_BAD_STATE; + } + + /* calculate nonce */ + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen = pgp_cipher_aead_nonce(skey.aalg, skey.iv, nonce, 0); + + /* start cipher, encrypt key and get tag */ + bool res = pgp_cipher_aead_start(&kcrypt, nonce, nlen) && + pgp_cipher_aead_finish(&kcrypt, skey.enckey, key, keylen); + + pgp_cipher_aead_destroy(&kcrypt); + + if (!res) { + return RNP_ERROR_BAD_STATE; + } +#endif + } + + /* Writing symmetric key encrypted session key packet */ + try { + skey.write(*param->pkt.origdst); + } catch (const std::exception &e) { + return RNP_ERROR_WRITE; + } + return param->pkt.origdst->werr; +} + +static rnp_result_t +encrypted_start_cfb(pgp_dest_encrypted_param_t *param, uint8_t *enckey) +{ + uint8_t mdcver = 1; + uint8_t enchdr[PGP_MAX_BLOCK_SIZE + 2]; /* encrypted header */ + unsigned blsize; + + if (param->auth_type == rnp::AuthType::MDC) { + /* initializing the mdc */ + dst_write(param->pkt.writedst, &mdcver, 1); + + try { + param->mdc = rnp::Hash::create(PGP_HASH_SHA1); + } catch (const std::exception &e) { + RNP_LOG("cannot create sha1 hash: %s", e.what()); + return RNP_ERROR_GENERIC; + } + } + + /* initializing the crypto */ + if (!pgp_cipher_cfb_start(¶m->encrypt, param->ctx->ealg, enckey, NULL)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* generating and writing iv/password check bytes */ + blsize = pgp_block_size(param->ctx->ealg); + try { + param->ctx->ctx->rng.get(enchdr, blsize); + enchdr[blsize] = enchdr[blsize - 2]; + enchdr[blsize + 1] = enchdr[blsize - 1]; + + if (param->auth_type == rnp::AuthType::MDC) { + param->mdc->add(enchdr, blsize + 2); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + + pgp_cipher_cfb_encrypt(¶m->encrypt, enchdr, enchdr, blsize + 2); + + /* RFC 4880, 5.13: Unlike the Symmetrically Encrypted Data Packet, no special CFB + * resynchronization is done after encrypting this prefix data. */ + if (param->auth_type == rnp::AuthType::None) { + pgp_cipher_cfb_resync(¶m->encrypt, enchdr + 2); + } + + dst_write(param->pkt.writedst, enchdr, blsize + 2); + + return RNP_SUCCESS; +} + +static rnp_result_t +encrypted_start_aead(pgp_dest_encrypted_param_t *param, uint8_t *enckey) +{ +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD support is not enabled."); + return RNP_ERROR_NOT_IMPLEMENTED; +#else + uint8_t hdr[4 + PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen; + + if (pgp_block_size(param->ctx->ealg) != 16) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* fill header */ + hdr[0] = 1; + hdr[1] = param->ctx->ealg; + hdr[2] = param->ctx->aalg; + hdr[3] = param->ctx->abits; + + /* generate iv */ + nlen = pgp_cipher_aead_nonce_len(param->ctx->aalg); + try { + param->ctx->ctx->rng.get(param->iv, nlen); + } catch (const std::exception &e) { + return RNP_ERROR_RNG; + } + memcpy(hdr + 4, param->iv, nlen); + + /* output header */ + dst_write(param->pkt.writedst, hdr, 4 + nlen); + + /* initialize encryption */ + param->chunklen = 1L << (hdr[3] + 6); + param->chunkout = 0; + + /* fill additional/authenticated data */ + param->ad[0] = PGP_PKT_AEAD_ENCRYPTED | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + memcpy(param->ad + 1, hdr, 4); + memset(param->ad + 5, 0, 8); + param->adlen = 13; + + /* initialize cipher */ + if (!pgp_cipher_aead_init( + ¶m->encrypt, param->ctx->ealg, param->ctx->aalg, enckey, false)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + return encrypted_start_aead_chunk(param, 0, false); +#endif +} + +static rnp_result_t +init_encrypted_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_encrypted_param_t *param; + bool singlepass = true; + unsigned pkeycount = 0; + unsigned skeycount = 0; + unsigned keylen; + rnp_result_t ret = RNP_ERROR_GENERIC; + + keylen = pgp_key_size(handler->ctx->ealg); + if (!keylen) { + RNP_LOG("unknown symmetric algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (handler->ctx->aalg) { + if ((handler->ctx->aalg != PGP_AEAD_EAX) && (handler->ctx->aalg != PGP_AEAD_OCB)) { + RNP_LOG("unknown AEAD algorithm: %d", (int) handler->ctx->aalg); + return RNP_ERROR_BAD_PARAMETERS; + } + + if ((pgp_block_size(handler->ctx->ealg) != 16)) { + RNP_LOG("wrong AEAD symmetric algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if ((handler->ctx->abits < 0) || (handler->ctx->abits > 16)) { + RNP_LOG("wrong AEAD chunk bits: %d", handler->ctx->abits); + return RNP_ERROR_BAD_PARAMETERS; + } + } + + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + try { + param = new pgp_dest_encrypted_param_t(); + dst->param = param; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + param->auth_type = + handler->ctx->aalg == PGP_AEAD_NONE ? rnp::AuthType::MDC : rnp::AuthType::AEADv1; + param->aalg = handler->ctx->aalg; + param->ctx = handler->ctx; + param->pkt.origdst = writedst; + dst->write = param->auth_type == rnp::AuthType::AEADv1 ? encrypted_dst_write_aead : + encrypted_dst_write_cfb; + dst->finish = encrypted_dst_finish; + dst->close = encrypted_dst_close; + dst->type = PGP_STREAM_ENCRYPTED; + + pkeycount = handler->ctx->recipients.size(); + skeycount = handler->ctx->passwords.size(); + + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> enckey; /* content encryption key */ + if (!pkeycount && !skeycount) { + RNP_LOG("no recipients"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if ((pkeycount > 0) || (skeycount > 1) || (param->auth_type == rnp::AuthType::AEADv1)) { + try { + handler->ctx->ctx->rng.get(enckey.data(), keylen); + } catch (const std::exception &e) { + ret = RNP_ERROR_RNG; + goto finish; + } + singlepass = false; + } + + /* Configuring and writing pk-encrypted session keys */ + for (auto recipient : handler->ctx->recipients) { + ret = encrypted_add_recipient(handler, dst, recipient, enckey.data(), keylen); + if (ret) { + goto finish; + } + } + + /* Configuring and writing sk-encrypted session key(s) */ + for (auto &passinfo : handler->ctx->passwords) { + ret = encrypted_add_password(&passinfo, param, enckey.data(), keylen, singlepass); + if (ret != RNP_SUCCESS) { + goto finish; + } + } + + /* Initializing partial packet writer */ + param->pkt.partial = true; + param->pkt.indeterminate = false; + if (param->auth_type == rnp::AuthType::AEADv1) { + param->pkt.tag = PGP_PKT_AEAD_ENCRYPTED; + } else { + /* We do not generate PGP_PKT_SE_DATA, leaving this just in case */ + param->pkt.tag = + param->auth_type == rnp::AuthType::MDC ? PGP_PKT_SE_IP_DATA : PGP_PKT_SE_DATA; + } + + /* initializing partial data length writer */ + /* we may use intederminate len packet here as well, for compatibility or so on */ + if (!init_streamed_packet(¶m->pkt, writedst)) { + RNP_LOG("failed to init streamed packet"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if (param->auth_type == rnp::AuthType::AEADv1) { + /* initialize AEAD encryption */ + ret = encrypted_start_aead(param, enckey.data()); + } else { + /* initialize old CFB or CFB with MDC */ + ret = encrypted_start_cfb(param, enckey.data()); + } +finish: + handler->ctx->passwords.clear(); + if (ret) { + encrypted_dst_close(dst, true); + } + return ret; +} + +static rnp_result_t +signed_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + dst_write(param->writedst, buf, len); + return RNP_SUCCESS; +} + +static void +cleartext_dst_writeline(pgp_dest_signed_param_t *param, + const uint8_t * buf, + size_t len, + bool eol) +{ + const uint8_t *ptr; + + /* dash-escaping line if needed */ + if (param->clr_start && len && + ((buf[0] == CH_DASH) || ((len >= 4) && !strncmp((const char *) buf, ST_FROM, 4)))) { + dst_write(param->writedst, ST_DASHSP, 2); + } + + /* output data */ + dst_write(param->writedst, buf, len); + + try { + if (eol) { + bool hashcrlf = false; + ptr = buf + len - 1; + + /* skipping trailing characters - space, tab, carriage return, line feed */ + while ((ptr >= buf) && ((*ptr == CH_SPACE) || (*ptr == CH_TAB) || + (*ptr == CH_CR) || (*ptr == CH_LF))) { + if (*ptr == CH_LF) { + hashcrlf = true; + } + ptr--; + } + + /* hashing line body and \r\n */ + param->hashes.add(buf, ptr + 1 - buf); + if (hashcrlf) { + param->hashes.add(ST_CRLF, 2); + } + param->clr_start = hashcrlf; + } else if (len > 0) { + /* hashing just line's data */ + param->hashes.add(buf, len); + param->clr_start = false; + } + } catch (const std::exception &e) { + RNP_LOG("failed to hash data: %s", e.what()); + } +} + +static size_t +cleartext_dst_scanline(const uint8_t *buf, size_t len, bool *eol) +{ + for (const uint8_t *ptr = buf, *end = buf + len; ptr < end; ptr++) { + if (*ptr == CH_LF) { + if (eol) { + *eol = true; + } + return ptr - buf + 1; + } + } + + if (eol) { + *eol = false; + } + return len; +} + +static rnp_result_t +cleartext_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + const uint8_t * linebg = (const uint8_t *) buf; + size_t linelen; + size_t cplen; + bool eol; + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + if (param->clr_buflen > 0) { + /* number of edge cases may happen here */ + linelen = cleartext_dst_scanline(linebg, len, &eol); + + if (param->clr_buflen + linelen < sizeof(param->clr_buf)) { + /* fits into buffer */ + memcpy(param->clr_buf + param->clr_buflen, linebg, linelen); + param->clr_buflen += linelen; + if (!eol) { + /* do not write the line if we don't have whole */ + return RNP_SUCCESS; + } + + cleartext_dst_writeline(param, param->clr_buf, param->clr_buflen, true); + param->clr_buflen = 0; + } else { + /* we have line longer than 4k */ + cplen = sizeof(param->clr_buf) - param->clr_buflen; + memcpy(param->clr_buf + param->clr_buflen, linebg, cplen); + cleartext_dst_writeline(param, param->clr_buf, sizeof(param->clr_buf), false); + + if (eol || (linelen >= sizeof(param->clr_buf))) { + cleartext_dst_writeline(param, linebg + cplen, linelen - cplen, eol); + param->clr_buflen = 0; + } else { + param->clr_buflen = linelen - cplen; + memcpy(param->clr_buf, linebg + cplen, param->clr_buflen); + return RNP_SUCCESS; + } + } + + linebg += linelen; + len -= linelen; + } + + /* if we get here then we don't have data in param->clr_buf */ + while (len > 0) { + linelen = cleartext_dst_scanline(linebg, len, &eol); + + if (!eol && (linelen < sizeof(param->clr_buf))) { + memcpy(param->clr_buf, linebg, linelen); + param->clr_buflen = linelen; + return RNP_SUCCESS; + } + + cleartext_dst_writeline(param, linebg, linelen, eol); + linebg += linelen; + len -= linelen; + } + + return RNP_SUCCESS; +} + +static void +signed_fill_signature(pgp_dest_signed_param_t ¶m, + pgp_signature_t & sig, + pgp_dest_signer_info_t & signer) +{ + /* fill signature fields, assuming sign_init was called on it */ + if (signer.sigcreate) { + sig.set_creation(signer.sigcreate); + } + sig.set_expiration(signer.sigexpire); + sig.fill_hashed_data(); + + auto listh = param.hashes.get(sig.halg); + if (!listh) { + RNP_LOG("failed to obtain hash"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* decrypt the secret key if needed */ + rnp::KeyLocker keylock(*signer.key); + if (signer.key->encrypted() && + !signer.key->unlock(*param.password_provider, PGP_OP_SIGN)) { + RNP_LOG("wrong secret key password"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PASSWORD); + } + /* calculate the signature */ + signature_calculate(sig, signer.key->material(), *listh->clone(), *param.ctx->ctx); +} + +static rnp_result_t +signed_write_signature(pgp_dest_signed_param_t *param, + pgp_dest_signer_info_t * signer, + pgp_dest_t * writedst) +{ + try { + pgp_signature_t sig; + if (signer->onepass.version) { + signer->key->sign_init(sig, signer->onepass.halg, param->ctx->ctx->time()); + sig.palg = signer->onepass.palg; + sig.set_type(signer->onepass.type); + } else { + signer->key->sign_init(sig, signer->halg, param->ctx->ctx->time()); + /* line below should be checked */ + sig.set_type(param->ctx->detached ? PGP_SIG_BINARY : PGP_SIG_TEXT); + } + signed_fill_signature(*param, sig, *signer); + sig.write(*writedst); + return writedst->werr; + } catch (const rnp::rnp_exception &e) { + return e.code(); + } catch (const std::exception &e) { + RNP_LOG("Failed to write signature: %s", e.what()); + return RNP_ERROR_WRITE; + } +} + +static rnp_result_t +signed_dst_finish(pgp_dest_t *dst) +{ + rnp_result_t ret; + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + /* attached signature, we keep onepasses in order of signatures */ + for (auto &sinfo : param->siginfos) { + if ((ret = signed_write_signature(param, &sinfo, param->writedst))) { + RNP_LOG("failed to calculate signature"); + return ret; + } + } + + return RNP_SUCCESS; +} + +static rnp_result_t +signed_detached_dst_finish(pgp_dest_t *dst) +{ + rnp_result_t ret; + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + /* just calculating and writing signatures to the output */ + for (auto &sinfo : param->siginfos) { + if ((ret = signed_write_signature(param, &sinfo, param->writedst))) { + RNP_LOG("failed to calculate detached signature"); + return ret; + } + } + + return RNP_SUCCESS; +} + +static rnp_result_t +cleartext_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + /* writing cached line if any */ + if (param->clr_buflen > 0) { + cleartext_dst_writeline(param, param->clr_buf, param->clr_buflen, true); + } + /* trailing \r\n which is not hashed */ + dst_write(param->writedst, ST_CRLF, 2); + + /* writing signatures to the armored stream, which outputs to param->writedst */ + try { + rnp::ArmoredDest armor(*param->writedst, PGP_ARMORED_SIGNATURE); + armor.set_discard(true); + for (auto &sinfo : param->siginfos) { + auto ret = signed_write_signature(param, &sinfo, &armor.dst()); + if (ret) { + return ret; + } + } + armor.set_discard(false); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("Failed to write armored signature: %s", e.what()); + return RNP_ERROR_WRITE; + } +} + +static void +signed_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + if (!param) { + return; + } + delete param; + dst->param = NULL; +} + +static void +signed_dst_update(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + param->hashes.add(buf, len); +} + +static rnp_result_t +signed_add_signer(pgp_dest_signed_param_t *param, rnp_signer_info_t *signer, bool last) +{ + pgp_dest_signer_info_t sinfo = {}; + + if (!signer->key->is_secret()) { + RNP_LOG("secret key required for signing"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* validate signing key material if didn't before */ + signer->key->pkt().material.validate(*param->ctx->ctx, false); + if (!signer->key->pkt().material.valid()) { + RNP_LOG("attempt to sign to the key with invalid material"); + return RNP_ERROR_NO_SUITABLE_KEY; + } + + /* copy fields */ + sinfo.key = signer->key; + sinfo.sigcreate = signer->sigcreate; + sinfo.sigexpire = signer->sigexpire; + + /* Add hash to the list */ + sinfo.halg = pgp_hash_adjust_alg_to_key(signer->halg, &signer->key->pkt()); + try { + param->hashes.add_alg(sinfo.halg); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_PARAMETERS; + } + + // Do not add onepass for detached/clearsign + if (param->ctx->detached || param->ctx->clearsign) { + sinfo.onepass.version = 0; + try { + param->siginfos.push_back(sinfo); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + + // Setup and add onepass + sinfo.onepass.version = 3; + sinfo.onepass.type = PGP_SIG_BINARY; + sinfo.onepass.halg = sinfo.halg; + sinfo.onepass.palg = sinfo.key->alg(); + sinfo.onepass.keyid = sinfo.key->keyid(); + sinfo.onepass.nested = false; + try { + param->siginfos.push_back(sinfo); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + // write onepasses in reverse order so signature order will match signers list + if (!last) { + return RNP_SUCCESS; + } + try { + for (auto it = param->siginfos.rbegin(); it != param->siginfos.rend(); it++) { + pgp_dest_signer_info_t &sinfo = *it; + sinfo.onepass.nested = &sinfo == ¶m->siginfos.front(); + sinfo.onepass.write(*param->writedst); + } + return param->writedst->werr; + } catch (const std::exception &e) { + return RNP_ERROR_WRITE; + } +} + +static rnp_result_t +init_signed_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_signed_param_t *param; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (!handler->key_provider) { + RNP_LOG("no key provider"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + try { + param = new pgp_dest_signed_param_t(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->param = param; + param->writedst = writedst; + param->ctx = handler->ctx; + param->password_provider = handler->password_provider; + if (param->ctx->clearsign) { + dst->type = PGP_STREAM_CLEARTEXT; + dst->write = cleartext_dst_write; + dst->finish = cleartext_dst_finish; + param->clr_start = true; + } else { + dst->type = PGP_STREAM_SIGNED; + dst->write = signed_dst_write; + dst->finish = param->ctx->detached ? signed_detached_dst_finish : signed_dst_finish; + } + dst->close = signed_dst_close; + + /* Getting signer's infos, writing one-pass signatures if needed */ + for (auto &sg : handler->ctx->signers) { + ret = signed_add_signer(param, &sg, &sg == &handler->ctx->signers.back()); + if (ret) { + RNP_LOG("failed to add one-pass signature for signer"); + goto finish; + } + } + + /* Do we have any signatures? */ + if (param->hashes.hashes.empty()) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + /* Writing headers for cleartext signed document */ + if (param->ctx->clearsign) { + dst_write(param->writedst, ST_CLEAR_BEGIN, strlen(ST_CLEAR_BEGIN)); + dst_write(param->writedst, ST_CRLF, strlen(ST_CRLF)); + dst_write(param->writedst, ST_HEADER_HASH, strlen(ST_HEADER_HASH)); + + for (const auto &hash : param->hashes.hashes) { + auto hname = rnp::Hash::name(hash->alg()); + dst_write(param->writedst, hname, strlen(hname)); + if (&hash != ¶m->hashes.hashes.back()) { + dst_write(param->writedst, ST_COMMA, 1); + } + } + + dst_write(param->writedst, ST_CRLFCRLF, strlen(ST_CRLFCRLF)); + } + + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + signed_dst_close(dst, true); + } + + return ret; +} + +static rnp_result_t +compressed_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param; + int zret; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + param->z.next_in = (unsigned char *) buf; + param->z.avail_in = len; + param->z.next_out = param->cache + param->len; + param->z.avail_out = sizeof(param->cache) - param->len; + + while (param->z.avail_in > 0) { + zret = deflate(¶m->z, Z_NO_FLUSH); + /* Z_OK, Z_BUF_ERROR are ok for us, Z_STREAM_END will not happen here */ + if (zret == Z_STREAM_ERROR) { + RNP_LOG("wrong deflate state"); + return RNP_ERROR_BAD_STATE; + } + + /* writing only full blocks, the rest will be written in close */ + if (param->z.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->z.next_out = param->cache; + param->z.avail_out = sizeof(param->cache); + } + } + + param->len = sizeof(param->cache) - param->z.avail_out; + return RNP_SUCCESS; + } else if (param->alg == PGP_C_BZIP2) { +#ifdef HAVE_BZLIB_H + param->bz.next_in = (char *) buf; + param->bz.avail_in = len; + param->bz.next_out = (char *) (param->cache + param->len); + param->bz.avail_out = sizeof(param->cache) - param->len; + + while (param->bz.avail_in > 0) { + zret = BZ2_bzCompress(¶m->bz, BZ_RUN); + if (zret < 0) { + RNP_LOG("error %d", zret); + return RNP_ERROR_BAD_STATE; + } + + /* writing only full blocks, the rest will be written in close */ + if (param->bz.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->bz.next_out = (char *) param->cache; + param->bz.avail_out = sizeof(param->cache); + } + } + + param->len = sizeof(param->cache) - param->bz.avail_out; + return RNP_SUCCESS; +#else + return RNP_ERROR_NOT_IMPLEMENTED; +#endif + } else { + RNP_LOG("unknown algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } +} + +static rnp_result_t +compressed_dst_finish(pgp_dest_t *dst) +{ + int zret; + pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param; + + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + param->z.next_in = Z_NULL; + param->z.avail_in = 0; + param->z.next_out = param->cache + param->len; + param->z.avail_out = sizeof(param->cache) - param->len; + do { + zret = deflate(¶m->z, Z_FINISH); + + if (zret == Z_STREAM_ERROR) { + RNP_LOG("wrong deflate state"); + return RNP_ERROR_BAD_STATE; + } + + if (param->z.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->z.next_out = param->cache; + param->z.avail_out = sizeof(param->cache); + } + } while (zret != Z_STREAM_END); + + param->len = sizeof(param->cache) - param->z.avail_out; + dst_write(param->pkt.writedst, param->cache, param->len); + } +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + param->bz.next_in = NULL; + param->bz.avail_in = 0; + param->bz.next_out = (char *) (param->cache + param->len); + param->bz.avail_out = sizeof(param->cache) - param->len; + + do { + zret = BZ2_bzCompress(¶m->bz, BZ_FINISH); + if (zret < 0) { + RNP_LOG("wrong bzip2 state %d", zret); + return RNP_ERROR_BAD_STATE; + } + + /* writing only full blocks, the rest will be written in close */ + if (param->bz.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->bz.next_out = (char *) param->cache; + param->bz.avail_out = sizeof(param->cache); + } + } while (zret != BZ_STREAM_END); + + param->len = sizeof(param->cache) - param->bz.avail_out; + dst_write(param->pkt.writedst, param->cache, param->len); + } +#endif + + if (param->pkt.writedst->werr) { + return param->pkt.writedst->werr; + } + + return finish_streamed_packet(¶m->pkt); +} + +static void +compressed_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param; + + if (!param) { + return; + } + + if (param->zstarted) { + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + deflateEnd(¶m->z); + } +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + BZ2_bzCompressEnd(¶m->bz); + } +#endif + } + + close_streamed_packet(¶m->pkt, discard); + free(param); + dst->param = NULL; +} + +static rnp_result_t +init_compressed_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_compressed_param_t *param; + rnp_result_t ret = RNP_ERROR_GENERIC; + uint8_t buf; + int zret; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_compressed_param_t *) dst->param; + dst->write = compressed_dst_write; + dst->finish = compressed_dst_finish; + dst->close = compressed_dst_close; + dst->type = PGP_STREAM_COMPRESSED; + param->alg = (pgp_compression_type_t) handler->ctx->zalg; + param->pkt.partial = true; + param->pkt.indeterminate = false; + param->pkt.tag = PGP_PKT_COMPRESSED; + + /* initializing partial length or indeterminate packet, writing header */ + if (!init_streamed_packet(¶m->pkt, writedst)) { + RNP_LOG("failed to init streamed packet"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + /* compression algorithm */ + buf = param->alg; + dst_write(param->pkt.writedst, &buf, 1); + + /* initializing compression */ + switch (param->alg) { + case PGP_C_ZIP: + case PGP_C_ZLIB: + (void) memset(¶m->z, 0x0, sizeof(param->z)); + if (param->alg == PGP_C_ZIP) { + zret = deflateInit2( + ¶m->z, handler->ctx->zlevel, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + } else { + zret = deflateInit(¶m->z, handler->ctx->zlevel); + } + + if (zret != Z_OK) { + RNP_LOG("failed to init zlib, error %d", zret); + ret = RNP_ERROR_NOT_SUPPORTED; + goto finish; + } + break; +#ifdef HAVE_BZLIB_H + case PGP_C_BZIP2: + (void) memset(¶m->bz, 0x0, sizeof(param->bz)); + zret = BZ2_bzCompressInit(¶m->bz, handler->ctx->zlevel, 0, 0); + if (zret != BZ_OK) { + RNP_LOG("failed to init bz, error %d", zret); + ret = RNP_ERROR_NOT_SUPPORTED; + goto finish; + } + break; +#endif + default: + RNP_LOG("unknown compression algorithm"); + ret = RNP_ERROR_NOT_SUPPORTED; + goto finish; + } + param->zstarted = true; + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + compressed_dst_close(dst, true); + } + + return ret; +} + +static rnp_result_t +literal_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_packet_param_t *param = (pgp_dest_packet_param_t *) dst->param; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + dst_write(param->writedst, buf, len); + return RNP_SUCCESS; +} + +static rnp_result_t +literal_dst_finish(pgp_dest_t *dst) +{ + return finish_streamed_packet((pgp_dest_packet_param_t *) dst->param); +} + +static void +literal_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_packet_param_t *param = (pgp_dest_packet_param_t *) dst->param; + + if (!param) { + return; + } + + close_streamed_packet(param, discard); + free(param); + dst->param = NULL; +} + +static rnp_result_t +init_literal_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_packet_param_t *param; + rnp_result_t ret = RNP_ERROR_GENERIC; + size_t flen = 0; + uint8_t buf[4]; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_packet_param_t *) dst->param; + dst->write = literal_dst_write; + dst->finish = literal_dst_finish; + dst->close = literal_dst_close; + dst->type = PGP_STREAM_LITERAL; + param->partial = true; + param->indeterminate = false; + param->tag = PGP_PKT_LITDATA; + + /* initializing partial length or indeterminate packet, writing header */ + if (!init_streamed_packet(param, writedst)) { + RNP_LOG("failed to init streamed packet"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + /* content type - forcing binary now */ + buf[0] = (uint8_t) 'b'; + /* filename */ + flen = handler->ctx->filename.size(); + if (flen > 255) { + RNP_LOG("filename too long, truncating"); + flen = 255; + } + buf[1] = (uint8_t) flen; + dst_write(param->writedst, buf, 2); + if (flen) { + dst_write(param->writedst, handler->ctx->filename.c_str(), flen); + } + /* timestamp */ + STORE32BE(buf, handler->ctx->filemtime); + dst_write(param->writedst, buf, 4); + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + literal_dst_close(dst, true); + } + + return ret; +} + +static rnp_result_t +process_stream_sequence(pgp_source_t *src, + pgp_dest_t * streams, + unsigned count, + pgp_dest_t * sstream, + pgp_dest_t * wstream) +{ + std::unique_ptr<uint8_t[]> readbuf(new (std::nothrow) uint8_t[PGP_INPUT_CACHE_SIZE]); + if (!readbuf) { + RNP_LOG("allocation failure"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + /* processing source stream */ + while (!src->eof) { + size_t read = 0; + if (!src_read(src, readbuf.get(), PGP_INPUT_CACHE_SIZE, &read)) { + RNP_LOG("failed to read from source"); + return RNP_ERROR_READ; + } else if (!read) { + continue; + } + + if (sstream) { + signed_dst_update(sstream, readbuf.get(), read); + } + + if (wstream) { + dst_write(wstream, readbuf.get(), read); + + for (int i = count - 1; i >= 0; i--) { + if (streams[i].werr) { + RNP_LOG("failed to process data"); + return RNP_ERROR_WRITE; + } + } + } + } + + /* finalizing destinations */ + for (int i = count - 1; i >= 0; i--) { + rnp_result_t ret = dst_finish(&streams[i]); + if (ret) { + RNP_LOG("failed to finish stream"); + return ret; + } + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst) +{ + /* stack of the streams would be as following: + [armoring stream] - if armoring is enabled + [compressing stream, partial writing stream] - compression is enabled, and not detached + signing stream + literal data stream, partial writing stream - if not detached or cleartext signature + */ + pgp_dest_t dests[4]; + unsigned destc = 0; + rnp_result_t ret = RNP_ERROR_GENERIC; + rnp_ctx_t & ctx = *handler->ctx; + pgp_dest_t * wstream = NULL; + pgp_dest_t * sstream = NULL; + + /* pushing armoring stream, which will write to the output */ + if (ctx.armor && !ctx.clearsign) { + pgp_armored_msg_t msgt = ctx.detached ? PGP_ARMORED_SIGNATURE : PGP_ARMORED_MESSAGE; + ret = init_armored_dst(&dests[destc], dst, msgt); + if (ret) { + goto finish; + } + destc++; + } + + /* if compression is enabled then pushing compressing stream */ + if (!ctx.detached && !ctx.clearsign && (ctx.zlevel > 0)) { + if ((ret = + init_compressed_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { + goto finish; + } + destc++; + } + + /* pushing signing stream, which will use handler->ctx to distinguish between + * attached/detached/cleartext signature */ + if ((ret = init_signed_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { + goto finish; + } + if (!ctx.clearsign) { + sstream = &dests[destc]; + } + if (!ctx.detached) { + wstream = &dests[destc]; + } + destc++; + + /* pushing literal data stream, if not detached/cleartext signature */ + if (!ctx.no_wrap && !ctx.detached && !ctx.clearsign) { + if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + wstream = &dests[destc]; + destc++; + } + + /* process source with streams stack */ + ret = process_stream_sequence(src, dests, destc, sstream, wstream); +finish: + for (int i = destc - 1; i >= 0; i--) { + dst_close(&dests[i], ret); + } + return ret; +} + +rnp_result_t +rnp_encrypt_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst) +{ + /* stack of the streams would be as following: + [armoring stream] - if armoring is enabled + [encrypting stream, partial writing stream] + [compressing stream, partial writing stream] - compression is enabled + signing stream + literal data stream, partial writing stream + */ + pgp_dest_t dests[5]; + size_t destc = 0; + rnp_result_t ret = RNP_SUCCESS; + rnp_ctx_t & ctx = *handler->ctx; + pgp_dest_t * sstream = NULL; + + /* we may use only attached signatures here */ + if (ctx.clearsign || ctx.detached) { + RNP_LOG("cannot clearsign or sign detached together with encryption"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* pushing armoring stream, which will write to the output */ + if (ctx.armor) { + if ((ret = init_armored_dst(&dests[destc], dst, PGP_ARMORED_MESSAGE))) { + goto finish; + } + destc++; + } + + /* pushing encrypting stream, which will write to the output or armoring stream */ + if ((ret = init_encrypted_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { + goto finish; + } + destc++; + + /* if compression is enabled then pushing compressing stream */ + if (ctx.zlevel > 0) { + if ((ret = init_compressed_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + destc++; + } + + /* pushing signing stream if we have signers */ + if (!ctx.signers.empty()) { + if ((ret = init_signed_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + sstream = &dests[destc]; + destc++; + } + + /* pushing literal data stream */ + if (!ctx.no_wrap) { + if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + destc++; + } + + /* process source with streams stack */ + ret = process_stream_sequence(src, dests, destc, sstream, &dests[destc - 1]); +finish: + for (size_t i = destc; i > 0; i--) { + dst_close(&dests[i - 1], ret); + } + return ret; +} + +rnp_result_t +rnp_compress_src(pgp_source_t &src, pgp_dest_t &dst, pgp_compression_type_t zalg, int zlevel) +{ + pgp_write_handler_t handler = {}; + rnp_ctx_t ctx; + ctx.zalg = zalg; + ctx.zlevel = zlevel; + handler.ctx = &ctx; + + pgp_dest_t compressed = {}; + rnp_result_t ret = init_compressed_dst(&handler, &compressed, &dst); + if (ret) { + goto done; + } + ret = dst_write_src(&src, &compressed); +done: + dst_close(&compressed, ret); + return ret; +} + +rnp_result_t +rnp_wrap_src(pgp_source_t &src, pgp_dest_t &dst, const std::string &filename, uint32_t modtime) +{ + pgp_write_handler_t handler = {}; + rnp_ctx_t ctx; + ctx.filename = filename; + ctx.filemtime = modtime; + handler.ctx = &ctx; + + pgp_dest_t literal = {}; + rnp_result_t ret = init_literal_dst(&handler, &literal, &dst); + if (ret) { + goto done; + } + + ret = dst_write_src(&src, &literal); +done: + dst_close(&literal, ret); + return ret; +} + +rnp_result_t +rnp_raw_encrypt_src(pgp_source_t & src, + pgp_dest_t & dst, + const std::string & password, + rnp::SecurityContext &secctx) +{ + pgp_write_handler_t handler = {}; + rnp_ctx_t ctx; + + ctx.ctx = &secctx; + ctx.ealg = DEFAULT_PGP_SYMM_ALG; + handler.ctx = &ctx; + pgp_dest_t encrypted = {}; + + rnp_result_t ret = RNP_ERROR_GENERIC; + try { + ret = + ctx.add_encryption_password(password, DEFAULT_PGP_HASH_ALG, DEFAULT_PGP_SYMM_ALG); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + goto done; + } + if (ret) { + goto done; + } + + ret = init_encrypted_dst(&handler, &encrypted, &dst); + if (ret) { + goto done; + } + + ret = dst_write_src(&src, &encrypted); +done: + dst_close(&encrypted, ret); + return ret; +} diff --git a/src/librepgp/stream-write.h b/src/librepgp/stream-write.h new file mode 100644 index 0000000..49431f9 --- /dev/null +++ b/src/librepgp/stream-write.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_WRITE_H_ +#define STREAM_WRITE_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "rnp.h" +#include "stream-common.h" +#include "stream-ctx.h" + +typedef struct pgp_write_handler_t { + pgp_password_provider_t *password_provider; + pgp_key_provider_t * key_provider; + rnp_ctx_t * ctx; + + void *param; +} pgp_write_handler_t; + +/** @brief sign the input data, producing attached, detached or cleartext signature. + * Type of the signature is controlled by clearsign and detached fields of the + * rnp_ctx_t structure + * @param handler handler to respond on stream processor callbacks, and additional processing + * parameters, including rnp_ctx_t + * @param src input source: file, stdin, memory, whatever else conforming to pgp_source_t + * @param dst output destination: file, stdout, memory, whatever else conforming to pgp_dest_t + **/ +rnp_result_t rnp_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst); + +/** @brief encrypt and sign the input data. Signatures will be enrypted together with data. + * @param handler handler handler to respond on stream processor callbacks, and additional + * processing parameters, including rnp_ctx_t + * @param src input source: file, stdin, memory, whatever else conforming to pgp_source_t + * @param dst output destination: file, stdout, memory, whatever else conforming to pgp_dest_t + **/ +rnp_result_t rnp_encrypt_sign_src(pgp_write_handler_t *handler, + pgp_source_t * src, + pgp_dest_t * dst); + +/* Following functions are used only in tests currently. Later could be used in CLI for debug + * commands like --wrap-literal, --encrypt-raw, --compress-raw, etc. */ + +rnp_result_t rnp_compress_src(pgp_source_t & src, + pgp_dest_t & dst, + pgp_compression_type_t zalg, + int zlevel); + +rnp_result_t rnp_wrap_src(pgp_source_t & src, + pgp_dest_t & dst, + const std::string &filename, + uint32_t modtime); + +rnp_result_t rnp_raw_encrypt_src(pgp_source_t & src, + pgp_dest_t & dst, + const std::string & password, + rnp::SecurityContext &secctx); + +#endif |