summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:32:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:32:49 +0000
commit8053187731ae8e3eb368d8360989cf5fd6eed9f7 (patch)
tree32bada84ff5d7460cdf3934fcbdbe770d6afe4cd /src/lib
parentInitial commit. (diff)
downloadrnp-8053187731ae8e3eb368d8360989cf5fd6eed9f7.tar.xz
rnp-8053187731ae8e3eb368d8360989cf5fd6eed9f7.zip
Adding upstream version 0.17.0.upstream/0.17.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rwxr-xr-xsrc/lib/CMakeLists.txt594
-rw-r--r--src/lib/config.h.in72
-rw-r--r--src/lib/crypto.cpp244
-rw-r--r--src/lib/crypto.h118
-rw-r--r--src/lib/crypto/backend_version.cpp184
-rw-r--r--src/lib/crypto/backend_version.h44
-rw-r--r--src/lib/crypto/bn.cpp107
-rw-r--r--src/lib/crypto/bn.h64
-rw-r--r--src/lib/crypto/bn_ossl.cpp84
-rw-r--r--src/lib/crypto/cipher.cpp78
-rw-r--r--src/lib/crypto/cipher.hpp77
-rw-r--r--src/lib/crypto/cipher_botan.cpp245
-rw-r--r--src/lib/crypto/cipher_botan.hpp73
-rw-r--r--src/lib/crypto/cipher_ossl.cpp284
-rw-r--r--src/lib/crypto/cipher_ossl.hpp80
-rw-r--r--src/lib/crypto/common.h51
-rw-r--r--src/lib/crypto/dl_ossl.cpp200
-rw-r--r--src/lib/crypto/dl_ossl.h44
-rw-r--r--src/lib/crypto/dsa.cpp382
-rw-r--r--src/lib/crypto/dsa.h141
-rw-r--r--src/lib/crypto/dsa_ossl.cpp355
-rw-r--r--src/lib/crypto/ec.cpp187
-rw-r--r--src/lib/crypto/ec.h173
-rw-r--r--src/lib/crypto/ec_curves.cpp323
-rw-r--r--src/lib/crypto/ec_ossl.cpp349
-rw-r--r--src/lib/crypto/ec_ossl.h42
-rw-r--r--src/lib/crypto/ecdh.cpp377
-rw-r--r--src/lib/crypto/ecdh.h117
-rw-r--r--src/lib/crypto/ecdh_ossl.cpp389
-rw-r--r--src/lib/crypto/ecdh_utils.cpp166
-rw-r--r--src/lib/crypto/ecdh_utils.h47
-rw-r--r--src/lib/crypto/ecdsa.cpp267
-rw-r--r--src/lib/crypto/ecdsa.h57
-rw-r--r--src/lib/crypto/ecdsa_ossl.cpp190
-rw-r--r--src/lib/crypto/eddsa.cpp212
-rw-r--r--src/lib/crypto/eddsa.h54
-rw-r--r--src/lib/crypto/eddsa_ossl.cpp156
-rw-r--r--src/lib/crypto/elgamal.cpp302
-rw-r--r--src/lib/crypto/elgamal.h116
-rw-r--r--src/lib/crypto/elgamal_ossl.cpp418
-rw-r--r--src/lib/crypto/hash.cpp160
-rw-r--r--src/lib/crypto/hash.hpp98
-rw-r--r--src/lib/crypto/hash_botan.hpp68
-rw-r--r--src/lib/crypto/hash_common.cpp194
-rw-r--r--src/lib/crypto/hash_crc24.cpp288
-rw-r--r--src/lib/crypto/hash_crc24.hpp47
-rw-r--r--src/lib/crypto/hash_ossl.cpp152
-rw-r--r--src/lib/crypto/hash_ossl.hpp55
-rw-r--r--src/lib/crypto/hash_sha1cd.cpp94
-rw-r--r--src/lib/crypto/hash_sha1cd.hpp52
-rw-r--r--src/lib/crypto/mem.cpp68
-rw-r--r--src/lib/crypto/mem.h158
-rw-r--r--src/lib/crypto/mem_ossl.cpp113
-rw-r--r--src/lib/crypto/mpi.cpp119
-rw-r--r--src/lib/crypto/mpi.h58
-rw-r--r--src/lib/crypto/ossl_common.h40
-rw-r--r--src/lib/crypto/rng.cpp59
-rw-r--r--src/lib/crypto/rng.h78
-rw-r--r--src/lib/crypto/rng_ossl.cpp48
-rw-r--r--src/lib/crypto/rsa.cpp419
-rw-r--r--src/lib/crypto/rsa.h90
-rw-r--r--src/lib/crypto/rsa_ossl.cpp629
-rw-r--r--src/lib/crypto/s2k.cpp203
-rw-r--r--src/lib/crypto/s2k.h65
-rw-r--r--src/lib/crypto/s2k_ossl.cpp97
-rw-r--r--src/lib/crypto/sha1cd/sha1.c2162
-rw-r--r--src/lib/crypto/sha1cd/sha1.h122
-rw-r--r--src/lib/crypto/sha1cd/ubc_check.c908
-rw-r--r--src/lib/crypto/sha1cd/ubc_check.h62
-rw-r--r--src/lib/crypto/signatures.cpp281
-rw-r--r--src/lib/crypto/signatures.h69
-rw-r--r--src/lib/crypto/sm2.cpp383
-rw-r--r--src/lib/crypto/sm2.h79
-rw-r--r--src/lib/crypto/sm2_ossl.cpp76
-rw-r--r--src/lib/crypto/symmetric.cpp648
-rw-r--r--src/lib/crypto/symmetric.h252
-rw-r--r--src/lib/crypto/symmetric_ossl.cpp644
-rw-r--r--src/lib/defaults.h86
-rw-r--r--src/lib/ffi-priv-types.h240
-rw-r--r--src/lib/fingerprint.cpp117
-rw-r--r--src/lib/fingerprint.h39
-rw-r--r--src/lib/generate-key.cpp467
-rw-r--r--src/lib/json-utils.cpp103
-rw-r--r--src/lib/json-utils.h91
-rw-r--r--src/lib/key-provider.cpp117
-rw-r--r--src/lib/key-provider.h120
-rw-r--r--src/lib/librnp.3.adoc89
-rw-r--r--src/lib/librnp.symbols1
-rw-r--r--src/lib/librnp.vsc4
-rw-r--r--src/lib/logging.cpp75
-rw-r--r--src/lib/logging.h97
-rw-r--r--src/lib/pass-provider.cpp56
-rw-r--r--src/lib/pass-provider.h61
-rw-r--r--src/lib/pgp-key.cpp2776
-rw-r--r--src/lib/pgp-key.h671
-rw-r--r--src/lib/rnp.cpp8403
-rw-r--r--src/lib/sec_profile.cpp228
-rw-r--r--src/lib/sec_profile.hpp112
-rw-r--r--src/lib/types.h482
-rw-r--r--src/lib/utils.cpp80
-rw-r--r--src/lib/utils.h101
-rw-r--r--src/lib/version.h.in52
-rw-r--r--src/librekey/g23_sexp.hpp71
-rw-r--r--src/librekey/kbx_blob.hpp162
-rw-r--r--src/librekey/key_store_g10.cpp1243
-rw-r--r--src/librekey/key_store_g10.h42
-rw-r--r--src/librekey/key_store_kbx.cpp706
-rw-r--r--src/librekey/key_store_kbx.h36
-rw-r--r--src/librekey/key_store_pgp.cpp241
-rw-r--r--src/librekey/key_store_pgp.h83
-rw-r--r--src/librekey/rnp_key_store.cpp803
-rw-r--r--src/librepgp/stream-armor.cpp1287
-rw-r--r--src/librepgp/stream-armor.h174
-rw-r--r--src/librepgp/stream-common.cpp1212
-rw-r--r--src/librepgp/stream-common.h556
-rw-r--r--src/librepgp/stream-ctx.cpp69
-rw-r--r--src/librepgp/stream-ctx.h123
-rw-r--r--src/librepgp/stream-def.h67
-rw-r--r--src/librepgp/stream-dump.cpp2533
-rw-r--r--src/librepgp/stream-dump.h53
-rw-r--r--src/librepgp/stream-key.cpp1469
-rw-r--r--src/librepgp/stream-key.h143
-rw-r--r--src/librepgp/stream-packet.cpp1228
-rw-r--r--src/librepgp/stream-packet.h323
-rw-r--r--src/librepgp/stream-parse.cpp2636
-rw-r--r--src/librepgp/stream-parse.h123
-rw-r--r--src/librepgp/stream-sig.cpp1557
-rw-r--r--src/librepgp/stream-sig.h437
-rw-r--r--src/librepgp/stream-write.cpp1973
-rw-r--r--src/librepgp/stream-write.h83
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 &params,
+ 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, &param->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, &param->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, &param->eol[0], 1);
+ }
+ if (param->eol[1]) {
+ dst_write(param->writedst, &param->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(&param->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, &param->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(&param->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(&param->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(&param->bz);
+ }
+#endif
+ if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) {
+ inflateEnd(&param->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(&param->decrypt);
+ }
+ STORE64BE(param->aead_ad + param->aead_adlen, total);
+ param->aead_adlen += 8;
+ }
+
+ if (!pgp_cipher_aead_set_ad(&param->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(&param->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(&param->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(&param->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(
+ &param->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(
+ &param->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(&param->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(&param->decrypt, mdcbuf, mdcbuf, MDC_V1_SIZE);
+ pgp_cipher_cfb_finish(&param->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(&param->decrypt);
+#endif
+ } else {
+ pgp_cipher_cfb_finish(&param->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 &param, 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 &param, 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 = &param->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(&param->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(
+ &param->decrypt, param->aead_hdr.ealg, param->aead_hdr.aalg, key, true)) {
+ return false;
+ }
+
+ gran = pgp_cipher_aead_granularity(&param->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 &param)
+{
+ param.origsrc = NULL;
+
+ /* save packet header */
+ rnp_result_t ret = stream_peek_packet_hdr(param.readsrc, &param.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(&param->z, 0x0, sizeof(param->z));
+ zret =
+ alg == PGP_C_ZIP ? (int) inflateInit2(&param->z, -15) : (int) inflateInit(&param->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(&param->bz, 0x0, sizeof(param->bz));
+ zret = BZ2_bzDecompressInit(&param->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, &param->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, &param->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, &param->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(&param->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, &param->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, &param->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(&param->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(
+ &param->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(&param->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(&param->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(&param->encrypt, nonce, nlen);
+
+ /* write final authentication tag */
+ if (last) {
+ res = res && pgp_cipher_aead_finish(&param->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(&param->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(&param->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(&param->encrypt);
+#endif
+ if (res) {
+ finish_streamed_packet(&param->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(&param->encrypt, mdcbuf, mdcbuf, MDC_V1_SIZE);
+ dst_write(param->pkt.writedst, mdcbuf, MDC_V1_SIZE);
+ }
+
+ return finish_streamed_packet(&param->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(&param->encrypt);
+#endif
+ } else {
+ pgp_cipher_cfb_finish(&param->encrypt);
+ }
+ close_streamed_packet(&param->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(&param->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(&param->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(&param->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(
+ &param->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(&param->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 &param,
+ 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 == &param->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 != &param->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(&param->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(&param->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(&param->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(&param->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(&param->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(&param->z);
+ }
+#ifdef HAVE_BZLIB_H
+ if (param->alg == PGP_C_BZIP2) {
+ BZ2_bzCompressEnd(&param->bz);
+ }
+#endif
+ }
+
+ close_streamed_packet(&param->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(&param->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(&param->z, 0x0, sizeof(param->z));
+ if (param->alg == PGP_C_ZIP) {
+ zret = deflateInit2(
+ &param->z, handler->ctx->zlevel, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
+ } else {
+ zret = deflateInit(&param->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(&param->bz, 0x0, sizeof(param->bz));
+ zret = BZ2_bzCompressInit(&param->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