diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b2a0b7..4efad84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,37 +3,22 @@ name: CI on: [push, pull_request] jobs: - # C_test: - # strategy: - # matrix: - # os: [ubuntu-latest] - # - # runs-on: ${{ matrix.os }} - # timeout-minutes: 30 - # - # steps: - # - name: Checkout code - # uses: actions/checkout@v6.0.2 - # - # - name: Install C dependencies (Ubuntu) - # if: matrix.os == 'ubuntu-latest' - # run: | - # sudo apt-get remove needrestart - # sudo apt-get update - # sudo apt-get install -y cmake - # - # - name: Build C code - # run: | - # mkdir -p build - # cd build - # cmake .. - # make - # - # - name: Run C tests - # run: | - # cd build - # ctest - + C_test: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout code + uses: actions/checkout@v6.0.2 + - name: Install C dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake libssl-dev build-essential + - name: Build C code + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + - name: Compile C code + run: cmake --build build --parallel + - name: Run C tests + run: ctest --test-dir build --output-on-failure Lint: runs-on: ubuntu-latest timeout-minutes: 5 diff --git a/.gitignore b/.gitignore index 5ca0ab2..b6b1847 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,23 @@ src/setup.json # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #cmake-build-* + +# ss_crypto build and test artifacts +/build/ +crypto/build/ +crypto/tests/*.log +crypto/tests/*.out +crypto/tests/*.err +crypto/tests/*.tmp +crypto/tests/*.key +crypto/tests/*.pem +*.o +*.d +*.so +*.a +*.dll +*.dylib +*.exe +*.core +*.pdb +*.map \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a14fd57 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,8 @@ +# Top-level build for ServSpy. C sources live in the self-contained +# crypto/ module; the Python package is managed by uv/pytest instead. +cmake_minimum_required(VERSION 3.16) +project(servspy VERSION 0.1.0 LANGUAGES C) + +include(CTest) +enable_testing() +add_subdirectory(crypto) diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt new file mode 100644 index 0000000..41dde43 --- /dev/null +++ b/crypto/CMakeLists.txt @@ -0,0 +1,90 @@ +# ss_crypto - ServSpy C/OpenSSL cryptography module. +# +# Usable both standalone (cmake -S crypto -B build) and as a subdirectory +# of a larger project (add_subdirectory(crypto)). + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + cmake_minimum_required(VERSION 3.16) + project(ss_crypto VERSION 1.0.0 LANGUAGES C) +endif() + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) +include(CTest) # defines BUILD_TESTING + +enable_testing() + +option(SS_CRYPTO_BUILD_TESTS "Build the ss_crypto test suite" ON) + +find_package(OpenSSL REQUIRED) + +set(SS_CRYPTO_SOURCES + src/ss_crypto.c + src/ss_rsa.c + src/ss_ecdh.c +) + +add_library(ss_crypto ${SS_CRYPTO_SOURCES}) +add_library(ss_crypto::ss_crypto ALIAS ss_crypto) + +target_include_directories(ss_crypto PUBLIC + $ + $ +) +target_link_libraries(ss_crypto PUBLIC OpenSSL::Crypto) + +set_target_properties(ss_crypto PROPERTIES + C_STANDARD 11 + C_STANDARD_REQUIRED ON + C_EXTENSIONS OFF + C_VISIBILITY_PRESET hidden + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + POSITION_INDEPENDENT_CODE ON +) + +if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(ss_crypto PRIVATE -Wall -Wextra) +endif() + +# ---- Install & export ----------------------------------------------------- +install(TARGETS ss_crypto + EXPORT ss_cryptoTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) +install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +install(EXPORT ss_cryptoTargets + FILE ss_cryptoTargets.cmake + NAMESPACE ss_crypto:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ss_crypto +) + +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ss_cryptoConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/ss_cryptoConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ss_crypto +) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/ss_cryptoConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/ss_cryptoConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/ss_cryptoConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ss_crypto +) + +# pkg-config file +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ss_crypto.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/ss_crypto.pc @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ss_crypto.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + +# ---- Tests ----------------------------------------------------------------- +if(SS_CRYPTO_BUILD_TESTS AND BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/crypto/cmake/ss_cryptoConfig.cmake.in b/crypto/cmake/ss_cryptoConfig.cmake.in new file mode 100644 index 0000000..05dba05 --- /dev/null +++ b/crypto/cmake/ss_cryptoConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/ss_cryptoTargets.cmake") + +check_required_components(ss_crypto) diff --git a/crypto/include/ss_crypto.h b/crypto/include/ss_crypto.h new file mode 100644 index 0000000..36d2b77 --- /dev/null +++ b/crypto/include/ss_crypto.h @@ -0,0 +1,95 @@ +/* + * ss_crypto.h - ServSpy crypto module, common public interface. + * + * A self-contained C/OpenSSL cryptography library providing: + * - RSA-OAEP key generation, PEM persistence, encrypt/decrypt + * - ECDH key agreement with HKDF-SHA256 session key derivation + * - AES-256-GCM authenticated encryption (seal/open) bound to an + * ECDH key pair, with implicit key confirmation of the peer + * + * Design rules: + * - Every function returns an ss_err_t; SS_OK (0) means success. + * - No global mutable state; the library is thread-safe as long as + * each handle is used by one thread at a time. + * - Output buffers supplied by the caller are never overrun; the + * "query length" pattern (out == NULL) returns the required size + * in *out_len. + * - Buffers returned via **out (PEM strings, seal/open results) are + * allocated with malloc(3) and MUST be released with free(3). + * + * Requires OpenSSL >= 1.1.1. Uses only the EVP high-level API. + */ +#ifndef SS_CRYPTO_H +#define SS_CRYPTO_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SS_CRYPTO_VERSION_MAJOR 1 +#define SS_CRYPTO_VERSION_MINOR 0 +#define SS_CRYPTO_VERSION_PATCH 0 +#define SS_CRYPTO_VERSION_STRING "1.0.0" + +/* DLL export/import for Windows shared builds; no-op elsewhere. */ +#if defined(_WIN32) +# if defined(SS_CRYPTO_SHARED) +# define SS_CRYPTO_API __declspec(dllexport) +# elif defined(SS_CRYPTO_USE_SHARED) +# define SS_CRYPTO_API __declspec(dllimport) +# else +# define SS_CRYPTO_API +# endif +#else +# define SS_CRYPTO_API __attribute__((visibility("default"))) +#endif + +/* Maximum HKDF-SHA256 output: 255 * 32 bytes (RFC 5869). */ +#define SS_CRYPTO_HKDF_SHA256_MAX_OUT 8160u + +typedef enum { + SS_OK = 0, /* success */ + SS_ERR_INVALID_ARG, /* NULL argument, illegal length, bad combination */ + SS_ERR_NOMEM, /* memory allocation failed */ + SS_ERR_OPENSSL, /* underlying OpenSSL operation failed; see + ss_crypto_openssl_errors() for detail */ + SS_ERR_IO, /* file could not be opened/read/written */ + SS_ERR_PARSE, /* PEM/DER input could not be parsed */ + SS_ERR_BUFFER_TOO_SMALL, /* caller-supplied output buffer too small */ + SS_ERR_AUTH_FAILED, /* AEAD tag verification failed */ + SS_ERR_UNSUPPORTED, /* unsupported algorithm or parameter */ + SS_ERR_DECRYPT, /* decryption failed for a non-authentication + reason (e.g. malformed RSA padding) */ +} ss_err_t; + +/* Human-readable description of an error code. Never returns NULL. */ +SS_CRYPTO_API const char *ss_err_string(ss_err_t err); + +/* + * Formats the pending OpenSSL error queue into buf (always NUL-terminated, + * truncated to buf_len). Each call consumes the queue; "no error" is written + * when the queue is empty. buf may be NULL with buf_len 0 to no-op. + */ +SS_CRYPTO_API void ss_crypto_openssl_errors(char *buf, size_t buf_len); + +/* + * HKDF (RFC 5869) with SHA-256: Extract-and-Expand. + * ikm - input key material (for ECDH: the raw shared secret) + * salt - optional salt; may be NULL/0 (HKDF zero-pads per RFC) + * info - optional context binding; may be NULL/0 + * out - output buffer of out_len bytes; out_len <= SS_CRYPTO_HKDF_SHA256_MAX_OUT + */ +SS_CRYPTO_API ss_err_t ss_crypto_hkdf_sha256( + const uint8_t *ikm, size_t ikm_len, + const uint8_t *salt, size_t salt_len, + const uint8_t *info, size_t info_len, + uint8_t *out, size_t out_len); + +#ifdef __cplusplus +} +#endif + +#endif /* SS_CRYPTO_H */ diff --git a/crypto/include/ss_ecdh.h b/crypto/include/ss_ecdh.h new file mode 100644 index 0000000..54980b5 --- /dev/null +++ b/crypto/include/ss_ecdh.h @@ -0,0 +1,120 @@ +/* + * ss_ecdh.h - ECDH key agreement with HKDF key derivation and + * AES-256-GCM authenticated encryption. + * + * Session model: + * 1. Each side generates a key pair (ss_ecdh_keypair_generate) and + * exchanges public keys as PEM (ss_ecdh_pub_to_pem / + * ss_ecdh_pub_from_pem). + * 2. ss_ecdh_derive_key derives a shared session key from the local + * private key and the peer public key. Both sides MUST pass the + * same salt and info. + * 3. ss_ecdh_seal / ss_ecdh_open provide a higher-level, + * self-contained AEAD: they derive the key internally with a + * random salt (prepended to the output) and with the two public + * keys bound as HKDF info, so the same pair of keys always agrees + * regardless of call order, and a MITM with a different key pair + * cannot produce a matching key. + * + * SECURITY: ECDH alone provides no authentication. The public keys + * must be exchanged over an authenticated channel (or signed/verified + * out of band). The peer key is checked to lie on the curve when + * parsed. + * + * Wire format of ss_ecdh_seal output (all lengths in bytes): + * [0:16] salt - random HKDF salt + * [16:28] iv - random 12-byte GCM nonce + * [28:28+N] ciphertext - N = plain_len + * [28+N:44+N] tag - 16-byte GCM authentication tag + * Overhead is therefore SS_ECDH_SEAL_OVERHEAD (44) bytes. + */ +#ifndef SS_ECDH_H +#define SS_ECDH_H + +#include "ss_crypto.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ss_ecdh_keypair ss_ecdh_keypair_t; /* local key pair (has private key) */ +typedef struct ss_ecdh_pubkey ss_ecdh_pubkey_t; /* peer public key only */ + +/* NIST curves supported by ss_ecdh_keypair_generate. */ +#define SS_ECDH_CURVE_P256 "P-256" +#define SS_ECDH_CURVE_P384 "P-384" +#define SS_ECDH_CURVE_P521 "P-521" + +/* Fixed overhead of the ss_ecdh_seal format: salt(16) + iv(12) + tag(16). */ +#define SS_ECDH_SEAL_OVERHEAD 44u + +/* + * Generate an ECDH key pair. curve: one of the SS_ECDH_CURVE_* macros + * (NULL selects P-256). Unknown curves return SS_ERR_UNSUPPORTED. + */ +SS_CRYPTO_API ss_err_t ss_ecdh_keypair_generate(const char *curve, + ss_ecdh_keypair_t **out_kp); + +/* Export the public key as a PEM SubjectPublicKeyInfo string + * (caller frees with free(3)). */ +SS_CRYPTO_API ss_err_t ss_ecdh_pub_to_pem(const ss_ecdh_keypair_t *kp, char **out_pem); + +/* Parse a peer public key from PEM. Verifies the point is on the curve. */ +SS_CRYPTO_API ss_err_t ss_ecdh_pub_from_pem(const char *pem, + ss_ecdh_pubkey_t **out_pub); + +/* Persist / load the private key (PKCS#8 PEM). When passphrase is + * non-NULL the key is encrypted with AES-256-CBC; the same passphrase + * is required to load it. */ +SS_CRYPTO_API ss_err_t ss_ecdh_keypair_write_priv(const ss_ecdh_keypair_t *kp, + const char *path, + const char *passphrase); +SS_CRYPTO_API ss_err_t ss_ecdh_keypair_read_priv(const char *path, + const char *passphrase, + ss_ecdh_keypair_t **out_kp); + +/* + * Derive a session key: shared secret -> HKDF-SHA256(salt, info). + * Both parties MUST supply identical salt and info; recommended info + * is the concatenation of both public keys (in a canonical order). + * out_key_len must be <= SS_CRYPTO_HKDF_SHA256_MAX_OUT. + */ +SS_CRYPTO_API ss_err_t ss_ecdh_derive_key(const ss_ecdh_keypair_t *self, + const ss_ecdh_pubkey_t *peer, + const uint8_t *salt, size_t salt_len, + const uint8_t *info, size_t info_len, + uint8_t *out_key, size_t out_key_len); + +/* + * Authenticated encryption to `peer`: derives an AES-256-GCM key via + * HKDF (random 16-byte salt, info = both public keys in byte-sorted + * canonical order) and produces the format described at the top of + * this file. aad (optional, may be NULL/0) is authenticated but not + * encrypted. On success *out_buf is malloc'd (free with free(3)). + * Empty plaintext (plain_len == 0) is permitted. + */ +SS_CRYPTO_API ss_err_t ss_ecdh_seal(const ss_ecdh_keypair_t *self, + const ss_ecdh_pubkey_t *peer, + const uint8_t *aad, size_t aad_len, + const uint8_t *plain, size_t plain_len, + uint8_t **out_buf, size_t *out_len); + +/* Inverse of ss_ecdh_seal. Returns SS_ERR_AUTH_FAILED on any + * tampering (bad tag), including a mismatched peer key. */ +SS_CRYPTO_API ss_err_t ss_ecdh_open(const ss_ecdh_keypair_t *self, + const ss_ecdh_pubkey_t *peer, + const uint8_t *aad, size_t aad_len, + const uint8_t *in_buf, size_t in_len, + uint8_t **out_plain, size_t *out_len); + +SS_CRYPTO_API void ss_ecdh_keypair_free(ss_ecdh_keypair_t *kp); +SS_CRYPTO_API void ss_ecdh_pubkey_free(ss_ecdh_pubkey_t *pub); + +#ifdef __cplusplus +} +#endif + +#endif /* SS_ECDH_H */ diff --git a/crypto/include/ss_rsa.h b/crypto/include/ss_rsa.h new file mode 100644 index 0000000..9ad7176 --- /dev/null +++ b/crypto/include/ss_rsa.h @@ -0,0 +1,84 @@ +/* + * ss_rsa.h - RSA-OAEP public-key encryption. + * + * Key generation, PEM persistence and encrypt/decrypt using RSA with + * OAEP padding and SHA-256 (RFC 8017). The OAEP hash is fixed to + * SHA-256; interoperating peers must use the same parameters. + * + * The key handle is an opaque struct; allocate/free with + * ss_rsa_keygen / ss_rsa_read_* / ss_rsa_key_free. + */ +#ifndef SS_RSA_H +#define SS_RSA_H + +#include "ss_crypto.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ss_rsa_key ss_rsa_key_t; + +/* Recommended minimum key size; ss_rsa_keygen accepts 2048..16384. */ +#define SS_RSA_MIN_BITS 2048u +#define SS_RSA_MAX_BITS 16384u + +/* + * Generate an RSA key pair of `bits` (2048, 3072 or 4096 recommended). + * On success *out_key receives a new handle (free with ss_rsa_key_free). + */ +SS_CRYPTO_API ss_err_t ss_rsa_keygen(int bits, ss_rsa_key_t **out_key); + +/* + * Read a public / private key from a PEM file. + * passphrase: required only for encrypted private keys, else NULL. + * A wrong passphrase is reported as SS_ERR_PARSE (OpenSSL does not + * reliably distinguish it from a malformed file). + * The returned handle carries whatever key material was loaded; a + * public-key-only handle can be used for encryption, and a private-key + * handle for decryption (it also contains the public key). + */ +SS_CRYPTO_API ss_err_t ss_rsa_read_pub(const char *path, ss_rsa_key_t **out_key); +SS_CRYPTO_API ss_err_t ss_rsa_read_priv(const char *path, const char *passphrase, + ss_rsa_key_t **out_key); + +/* + * Write the public key (SubjectPublicKeyInfo) / private key (PKCS#8) + * to a PEM file. When passphrase is non-NULL the private key is + * encrypted with AES-256-CBC. The output file is overwritten. + */ +SS_CRYPTO_API ss_err_t ss_rsa_write_pub(const ss_rsa_key_t *key, const char *path); +SS_CRYPTO_API ss_err_t ss_rsa_write_priv(const ss_rsa_key_t *key, const char *path, + const char *passphrase); + +/* + * RSA-OAEP (SHA-256) encrypt/decrypt. Binary-safe: operates on + * in_len bytes, including embedded NUL bytes. + * + * Query pattern: pass out == NULL to obtain the required buffer size + * in *out_len, then call again with a buffer of that size. + * Encrypting more than ss_rsa_max_plaintext_len() bytes returns + * SS_ERR_INVALID_ARG. + */ +SS_CRYPTO_API ss_err_t ss_rsa_encrypt(const ss_rsa_key_t *key, + const uint8_t *in, size_t in_len, + uint8_t *out, size_t *out_len); +SS_CRYPTO_API ss_err_t ss_rsa_decrypt(const ss_rsa_key_t *key, + const uint8_t *in, size_t in_len, + uint8_t *out, size_t *out_len); + +/* Size of the ciphertext for this key (== RSA modulus size in bytes). */ +SS_CRYPTO_API size_t ss_rsa_ciphertext_len(const ss_rsa_key_t *key); + +/* Maximum plaintext length for OAEP-SHA256 with this key. */ +SS_CRYPTO_API size_t ss_rsa_max_plaintext_len(const ss_rsa_key_t *key); + +SS_CRYPTO_API void ss_rsa_key_free(ss_rsa_key_t *key); + +#ifdef __cplusplus +} +#endif + +#endif /* SS_RSA_H */ diff --git a/crypto/src/ss_crypto.c b/crypto/src/ss_crypto.c new file mode 100644 index 0000000..b00c846 --- /dev/null +++ b/crypto/src/ss_crypto.c @@ -0,0 +1,116 @@ +/* + * ss_crypto.c - common utilities: error strings, OpenSSL error dump, + * HKDF-SHA256. + */ +#include "ss_crypto.h" + +#include +#include + +#include +#include +#include + +#define COLLECT_ERRORS_MAX 4096 + +static const char *const kErrStrings[] = { + "success", /* SS_OK */ + "invalid argument", /* SS_ERR_INVALID_ARG */ + "out of memory", /* SS_ERR_NOMEM */ + "OpenSSL operation failed", /* SS_ERR_OPENSSL */ + "I/O error", /* SS_ERR_IO */ + "parse error", /* SS_ERR_PARSE */ + "output buffer too small", /* SS_ERR_BUFFER_TOO_SMALL */ + "authentication failed", /* SS_ERR_AUTH_FAILED */ + "unsupported algorithm or parameter", /* SS_ERR_UNSUPPORTED */ + "decryption failed", /* SS_ERR_DECRYPT */ +}; + +_Static_assert(sizeof(kErrStrings) / sizeof(kErrStrings[0]) == SS_ERR_DECRYPT + 1, + "error string table out of sync with ss_err_t"); + +const char *ss_err_string(ss_err_t err) { + if (err < 0 || (size_t)err >= sizeof(kErrStrings) / sizeof(kErrStrings[0])) { + return "unknown error"; + } + return kErrStrings[err]; +} + +typedef struct { + char buf[COLLECT_ERRORS_MAX]; + size_t pos; +} err_sink_t; + +static int collect_errors(const char *str, size_t len, void *u) { + err_sink_t *sink = (err_sink_t *)u; + size_t copy = sink->pos + len < sizeof(sink->buf) ? len : sizeof(sink->buf) - sink->pos; + if (copy > 0) { + memcpy(sink->buf + sink->pos, str, copy); + sink->pos += copy; + } + return 1; /* keep iterating */ +} + +void ss_crypto_openssl_errors(char *out_buf, size_t buf_len) { + if (out_buf == NULL || buf_len == 0) { + return; + } + err_sink_t sink = {0}; + ERR_print_errors_cb(collect_errors, &sink); + if (sink.pos == 0) { + snprintf(out_buf, buf_len, "no error"); + } else { + size_t copy = sink.pos < buf_len - 1 ? sink.pos : buf_len - 1; + memcpy(out_buf, sink.buf, copy); + out_buf[copy] = '\0'; + } +} + +/* OpenSSL 3.0+ uses EVP_PKEY_CTX_add1_hkdf_info, not set1. */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L +# define EV_PKEY_CTX_HKDF_INFO_SET EVP_PKEY_CTX_set1_hkdf_info +#else +# define EV_PKEY_CTX_HKDF_INFO_SET EVP_PKEY_CTX_add1_hkdf_info +#endif + +ss_err_t ss_crypto_hkdf_sha256(const uint8_t *ikm, size_t ikm_len, + const uint8_t *salt, size_t salt_len, + const uint8_t *info, size_t info_len, + uint8_t *out, size_t out_len) { + if (ikm == NULL || ikm_len == 0) { + return SS_ERR_INVALID_ARG; + } + if (out == NULL || out_len == 0 || out_len > SS_CRYPTO_HKDF_SHA256_MAX_OUT) { + return SS_ERR_INVALID_ARG; + } + if ((salt_len > 0 && salt == NULL) || (info_len > 0 && info == NULL)) { + return SS_ERR_INVALID_ARG; + } + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (ctx == NULL) { + return SS_ERR_OPENSSL; + } + if (EVP_PKEY_derive_init(ctx) <= 0 || + EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) <= 0 || + EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256()) <= 0 || + EVP_PKEY_CTX_set1_hkdf_key(ctx, ikm, ikm_len) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + if (salt_len > 0 && EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt, salt_len) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + if (info_len > 0 && EV_PKEY_CTX_HKDF_INFO_SET(ctx, info, info_len) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + size_t actual = out_len; + if (EVP_PKEY_derive(ctx, out, &actual) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + EVP_PKEY_CTX_free(ctx); + return SS_OK; +} \ No newline at end of file diff --git a/crypto/src/ss_ecdh.c b/crypto/src/ss_ecdh.c new file mode 100644 index 0000000..78bb203 --- /dev/null +++ b/crypto/src/ss_ecdh.c @@ -0,0 +1,563 @@ +/* + * ss_ecdh.c - ECDH key agreement (EVP high-level API), HKDF-SHA256 + * session key derivation, and AES-256-GCM seal/open bound to the + * key pair. + */ +#include "ss_ecdh.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define SS_ECDH_SALT_LEN 16u +#define SS_ECDH_IV_LEN 12u +#define SS_ECDH_TAG_LEN 16u +#define SS_ECDH_KEY_LEN 32u /* AES-256 */ + +struct ss_ecdh_keypair { + EVP_PKEY *pkey; +}; + +struct ss_ecdh_pubkey { + EVP_PKEY *pkey; +}; + +static int curve_nid(const char *curve) { + if (curve == NULL || strcmp(curve, SS_ECDH_CURVE_P256) == 0) { + return NID_X9_62_prime256v1; + } + if (strcmp(curve, SS_ECDH_CURVE_P384) == 0) { + return NID_secp384r1; + } + if (strcmp(curve, SS_ECDH_CURVE_P521) == 0) { + return NID_secp521r1; + } + return 0; +} + +ss_err_t ss_ecdh_keypair_generate(const char *curve, ss_ecdh_keypair_t **out_kp) { + if (out_kp == NULL) { + return SS_ERR_INVALID_ARG; + } + int nid = curve_nid(curve); + if (nid == 0) { + return SS_ERR_UNSUPPORTED; + } + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + if (ctx == NULL) { + return SS_ERR_OPENSSL; + } + if (EVP_PKEY_keygen_init(ctx) <= 0 || + EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + EVP_PKEY *pkey = NULL; + if (EVP_PKEY_keygen(ctx, &pkey) <= 0 || pkey == NULL) { + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + EVP_PKEY_CTX_free(ctx); + + ss_ecdh_keypair_t *kp = (ss_ecdh_keypair_t *)calloc(1, sizeof(*kp)); + if (kp == NULL) { + EVP_PKEY_free(pkey); + return SS_ERR_NOMEM; + } + kp->pkey = pkey; + *out_kp = kp; + return SS_OK; +} + +ss_err_t ss_ecdh_pub_to_pem(const ss_ecdh_keypair_t *kp, char **out_pem) { + if (kp == NULL || kp->pkey == NULL || out_pem == NULL) { + return SS_ERR_INVALID_ARG; + } + BIO *bio = BIO_new(BIO_s_mem()); + if (bio == NULL) { + return SS_ERR_NOMEM; + } + if (PEM_write_bio_PUBKEY(bio, kp->pkey) <= 0) { + BIO_free(bio); + return SS_ERR_OPENSSL; + } + char *data = NULL; + long n = BIO_get_mem_data(bio, &data); + if (n <= 0) { + BIO_free(bio); + return SS_ERR_OPENSSL; + } + char *out = (char *)malloc((size_t)n + 1); + if (out == NULL) { + BIO_free(bio); + return SS_ERR_NOMEM; + } + memcpy(out, data, (size_t)n); + out[n] = '\0'; + BIO_free(bio); + *out_pem = out; + return SS_OK; +} + +ss_err_t ss_ecdh_pub_from_pem(const char *pem, ss_ecdh_pubkey_t **out_pub) { + if (pem == NULL || out_pub == NULL) { + return SS_ERR_INVALID_ARG; + } + BIO *bio = BIO_new_mem_buf(pem, -1); + if (bio == NULL) { + return SS_ERR_NOMEM; + } + EVP_PKEY *pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + BIO_free(bio); + if (pkey == NULL) { + return SS_ERR_PARSE; + } + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { + EVP_PKEY_free(pkey); + return SS_ERR_UNSUPPORTED; + } + /* Reject points that are not on the curve (invalid-curve defense). */ + EVP_PKEY_CTX *chk = EVP_PKEY_CTX_new(pkey, NULL); + if (chk == NULL) { + EVP_PKEY_free(pkey); + return SS_ERR_OPENSSL; + } + int rc = EVP_PKEY_public_check(chk); + EVP_PKEY_CTX_free(chk); + if (rc != 1) { + EVP_PKEY_free(pkey); + return SS_ERR_PARSE; + } + + ss_ecdh_pubkey_t *pub = (ss_ecdh_pubkey_t *)calloc(1, sizeof(*pub)); + if (pub == NULL) { + EVP_PKEY_free(pkey); + return SS_ERR_NOMEM; + } + pub->pkey = pkey; + *out_pub = pub; + return SS_OK; +} + +static ss_err_t read_private_key(const char *path, const char *passphrase, + ss_ecdh_keypair_t **out_kp) { + if (path == NULL || out_kp == NULL) { + return SS_ERR_INVALID_ARG; + } + BIO *bio = BIO_new_file(path, "rb"); + if (bio == NULL) { + return SS_ERR_IO; + } + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, (void *)passphrase); + BIO_free(bio); + if (pkey == NULL) { + return SS_ERR_PARSE; /* covers malformed PEM and wrong passphrase */ + } + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { + EVP_PKEY_free(pkey); + return SS_ERR_UNSUPPORTED; + } + ss_ecdh_keypair_t *kp = (ss_ecdh_keypair_t *)calloc(1, sizeof(*kp)); + if (kp == NULL) { + EVP_PKEY_free(pkey); + return SS_ERR_NOMEM; + } + kp->pkey = pkey; + *out_kp = kp; + return SS_OK; +} + +ss_err_t ss_ecdh_keypair_write_priv(const ss_ecdh_keypair_t *kp, const char *path, + const char *passphrase) { + if (kp == NULL || kp->pkey == NULL || path == NULL) { + return SS_ERR_INVALID_ARG; + } + BIO *bio = BIO_new_file(path, "wb"); + if (bio == NULL) { + return SS_ERR_IO; + } + const EVP_CIPHER *cipher = passphrase != NULL ? EVP_aes_256_cbc() : NULL; + int rc = PEM_write_bio_PKCS8PrivateKey(bio, kp->pkey, cipher, NULL, 0, NULL, + (void *)passphrase); + BIO_free(bio); + return rc == 1 ? SS_OK : SS_ERR_OPENSSL; +} + +ss_err_t ss_ecdh_keypair_read_priv(const char *path, const char *passphrase, + ss_ecdh_keypair_t **out_kp) { + return read_private_key(path, passphrase, out_kp); +} + +/* + * Compute the raw ECDH shared secret (malloc'd, caller frees). + */ +static ss_err_t derive_shared_secret(EVP_PKEY *self, EVP_PKEY *peer, + uint8_t **out_secret, size_t *out_len) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(self, NULL); + if (ctx == NULL) { + return SS_ERR_OPENSSL; + } + if (EVP_PKEY_derive_init(ctx) <= 0 || EVP_PKEY_derive_set_peer(ctx, peer) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + size_t secret_len = 0; + if (EVP_PKEY_derive(ctx, NULL, &secret_len) <= 0 || secret_len == 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + uint8_t *secret = (uint8_t *)malloc(secret_len); + if (secret == NULL) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_NOMEM; + } + if (EVP_PKEY_derive(ctx, secret, &secret_len) <= 0) { + OPENSSL_cleanse(secret, secret_len); + free(secret); + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + EVP_PKEY_CTX_free(ctx); + *out_secret = secret; + *out_len = secret_len; + return SS_OK; +} + +/* + * HKDF info binding: DER encodings of both public keys, byte-sorted so + * that the two peers construct identical info regardless of call order. + */ +static ss_err_t build_key_binding_info(EVP_PKEY *a, EVP_PKEY *b, + uint8_t **out_info, size_t *out_len) { + uint8_t *der_a = NULL; + uint8_t *der_b = NULL; + int len_a = i2d_PUBKEY(a, &der_a); + int len_b = i2d_PUBKEY(b, &der_b); + if (len_a <= 0 || len_b <= 0) { + OPENSSL_free(der_a); + OPENSSL_free(der_b); + return SS_ERR_OPENSSL; + } + const uint8_t *first; + const uint8_t *second; + size_t first_len, second_len; + if ((size_t)len_a < (size_t)len_b || + ((size_t)len_a == (size_t)len_b && memcmp(der_a, der_b, (size_t)len_a) < 0)) { + first = der_a; + first_len = (size_t)len_a; + second = der_b; + second_len = (size_t)len_b; + } else { + first = der_b; + first_len = (size_t)len_b; + second = der_a; + second_len = (size_t)len_a; + } + uint8_t *info = (uint8_t *)malloc(first_len + second_len); + if (info == NULL) { + OPENSSL_free(der_a); + OPENSSL_free(der_b); + return SS_ERR_NOMEM; + } + memcpy(info, first, first_len); + memcpy(info + first_len, second, second_len); + OPENSSL_free(der_a); + OPENSSL_free(der_b); + *out_info = info; + *out_len = first_len + second_len; + return SS_OK; +} + +ss_err_t ss_ecdh_derive_key(const ss_ecdh_keypair_t *self, const ss_ecdh_pubkey_t *peer, + const uint8_t *salt, size_t salt_len, + const uint8_t *info, size_t info_len, + uint8_t *out_key, size_t out_key_len) { + if (self == NULL || self->pkey == NULL || peer == NULL || peer->pkey == NULL || + out_key == NULL) { + return SS_ERR_INVALID_ARG; + } + if (out_key_len == 0 || out_key_len > SS_CRYPTO_HKDF_SHA256_MAX_OUT) { + return SS_ERR_INVALID_ARG; + } + if ((salt_len > 0 && salt == NULL) || (info_len > 0 && info == NULL)) { + return SS_ERR_INVALID_ARG; + } + uint8_t *secret = NULL; + size_t secret_len = 0; + ss_err_t err = derive_shared_secret(self->pkey, peer->pkey, &secret, &secret_len); + if (err != SS_OK) { + return err; + } + err = ss_crypto_hkdf_sha256(secret, secret_len, salt, salt_len, info, info_len, + out_key, out_key_len); + OPENSSL_cleanse(secret, secret_len); + free(secret); + return err; +} + +/* + * GCM's output length fits in int for inputs below INT_MAX - 16. + */ +static int gcm_len_ok(size_t len) { + return len <= (size_t)(INT_MAX - 16); +} + +ss_err_t ss_ecdh_seal(const ss_ecdh_keypair_t *self, const ss_ecdh_pubkey_t *peer, + const uint8_t *aad, size_t aad_len, + const uint8_t *plain, size_t plain_len, + uint8_t **out_buf, size_t *out_len) { + if (self == NULL || self->pkey == NULL || peer == NULL || peer->pkey == NULL || + out_buf == NULL || out_len == NULL) { + return SS_ERR_INVALID_ARG; + } + if ((plain_len > 0 && plain == NULL) || (aad_len > 0 && aad == NULL)) { + return SS_ERR_INVALID_ARG; + } + if (plain_len > SIZE_MAX - SS_ECDH_SEAL_OVERHEAD || !gcm_len_ok(plain_len) || + !gcm_len_ok(aad_len)) { + return SS_ERR_INVALID_ARG; + } + + uint8_t *secret = NULL; + uint8_t *info = NULL; + uint8_t *key = NULL; + uint8_t *buf = NULL; + EVP_CIPHER_CTX *ctx = NULL; + size_t secret_len = 0, info_len = 0; + ss_err_t err; + + err = derive_shared_secret(self->pkey, peer->pkey, &secret, &secret_len); + if (err != SS_OK) { + return err; + } + err = build_key_binding_info(self->pkey, peer->pkey, &info, &info_len); + if (err != SS_OK) { + goto out; + } + uint8_t salt[SS_ECDH_SALT_LEN]; + uint8_t iv[SS_ECDH_IV_LEN]; + if (RAND_bytes(salt, sizeof(salt)) != 1 || RAND_bytes(iv, sizeof(iv)) != 1) { + err = SS_ERR_OPENSSL; + goto out; + } + key = (uint8_t *)malloc(SS_ECDH_KEY_LEN); + if (key == NULL) { + err = SS_ERR_NOMEM; + goto out; + } + err = ss_crypto_hkdf_sha256(secret, secret_len, salt, sizeof(salt), info, info_len, + key, SS_ECDH_KEY_LEN); + if (err != SS_OK) { + goto out; + } + + size_t total = SS_ECDH_SEAL_OVERHEAD + plain_len; + buf = (uint8_t *)malloc(total); + if (buf == NULL) { + err = SS_ERR_NOMEM; + goto out; + } + memcpy(buf, salt, sizeof(salt)); + memcpy(buf + SS_ECDH_SALT_LEN, iv, sizeof(iv)); + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + err = SS_ERR_NOMEM; + goto out; + } + int len = 0, outlen = 0; + if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv)) { + err = SS_ERR_OPENSSL; + goto out; + } + if (aad_len > 0 && !EVP_EncryptUpdate(ctx, NULL, &len, aad, (int)aad_len)) { + err = SS_ERR_OPENSSL; + goto out; + } + if (plain_len > 0 && + !EVP_EncryptUpdate(ctx, buf + SS_ECDH_SALT_LEN + SS_ECDH_IV_LEN, &len, + plain, (int)plain_len)) { + err = SS_ERR_OPENSSL; + goto out; + } + outlen = len; + if (!EVP_EncryptFinal_ex(ctx, buf + SS_ECDH_SALT_LEN + SS_ECDH_IV_LEN + outlen, + &len)) { + err = SS_ERR_OPENSSL; + goto out; + } + outlen += len; + if (outlen != (int)plain_len || + !EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, SS_ECDH_TAG_LEN, + buf + SS_ECDH_SALT_LEN + SS_ECDH_IV_LEN + outlen)) { + err = SS_ERR_OPENSSL; + goto out; + } + *out_buf = buf; + *out_len = SS_ECDH_SALT_LEN + SS_ECDH_IV_LEN + (size_t)outlen + SS_ECDH_TAG_LEN; + buf = NULL; + err = SS_OK; + +out: + if (ctx != NULL) { + EVP_CIPHER_CTX_free(ctx); + } + if (buf != NULL) { + free(buf); + } + if (key != NULL) { + OPENSSL_cleanse(key, SS_ECDH_KEY_LEN); + free(key); + } + if (info != NULL) { + free(info); + } + if (secret != NULL) { + OPENSSL_cleanse(secret, secret_len); + free(secret); + } + return err; +} + +ss_err_t ss_ecdh_open(const ss_ecdh_keypair_t *self, const ss_ecdh_pubkey_t *peer, + const uint8_t *aad, size_t aad_len, + const uint8_t *in_buf, size_t in_len, + uint8_t **out_plain, size_t *out_len) { + if (self == NULL || self->pkey == NULL || peer == NULL || peer->pkey == NULL || + in_buf == NULL || out_plain == NULL || out_len == NULL) { + return SS_ERR_INVALID_ARG; + } + *out_plain = NULL; + *out_len = 0; + if (aad_len > 0 && aad == NULL) { + return SS_ERR_INVALID_ARG; + } + if (in_len < SS_ECDH_SEAL_OVERHEAD) { + return SS_ERR_INVALID_ARG; + } + size_t ct_len = in_len - SS_ECDH_SEAL_OVERHEAD; + if (!gcm_len_ok(ct_len) || !gcm_len_ok(aad_len)) { + return SS_ERR_INVALID_ARG; + } + const uint8_t *salt = in_buf; + const uint8_t *iv = in_buf + SS_ECDH_SALT_LEN; + const uint8_t *ct = in_buf + SS_ECDH_SALT_LEN + SS_ECDH_IV_LEN; + const uint8_t *tag = ct + ct_len; + + uint8_t *secret = NULL; + uint8_t *info = NULL; + uint8_t *key = NULL; + uint8_t *plain = NULL; + EVP_CIPHER_CTX *ctx = NULL; + size_t secret_len = 0, info_len = 0; + ss_err_t err; + + err = derive_shared_secret(self->pkey, peer->pkey, &secret, &secret_len); + if (err != SS_OK) { + return err; + } + err = build_key_binding_info(self->pkey, peer->pkey, &info, &info_len); + if (err != SS_OK) { + goto out; + } + key = (uint8_t *)malloc(SS_ECDH_KEY_LEN); + if (key == NULL) { + err = SS_ERR_NOMEM; + goto out; + } + err = ss_crypto_hkdf_sha256(secret, secret_len, salt, SS_ECDH_SALT_LEN, info, + info_len, key, SS_ECDH_KEY_LEN); + if (err != SS_OK) { + goto out; + } + + plain = (uint8_t *)malloc(ct_len > 0 ? ct_len : 1); + if (plain == NULL) { + err = SS_ERR_NOMEM; + goto out; + } + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + err = SS_ERR_NOMEM; + goto out; + } + int len = 0, outlen = 0; + if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv)) { + err = SS_ERR_OPENSSL; + goto out; + } + if (aad_len > 0 && !EVP_DecryptUpdate(ctx, NULL, &len, aad, (int)aad_len)) { + err = SS_ERR_OPENSSL; + goto out; + } + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, SS_ECDH_TAG_LEN, + (void *)tag)) { + err = SS_ERR_OPENSSL; + goto out; + } + if (ct_len > 0 && + !EVP_DecryptUpdate(ctx, plain, &len, ct, (int)ct_len)) { + err = SS_ERR_OPENSSL; + goto out; + } + outlen = len; + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, SS_ECDH_TAG_LEN, + (void *)tag)) { + err = SS_ERR_OPENSSL; + goto out; + } + if (!EVP_DecryptFinal_ex(ctx, plain + outlen, &len)) { + err = SS_ERR_AUTH_FAILED; /* tampered data or wrong peer key */ + goto out; + } + outlen += len; + *out_plain = plain; + *out_len = (size_t)outlen; + plain = NULL; + err = SS_OK; + +out: + if (ctx != NULL) { + EVP_CIPHER_CTX_free(ctx); + } + if (plain != NULL) { + free(plain); + } + if (key != NULL) { + OPENSSL_cleanse(key, SS_ECDH_KEY_LEN); + free(key); + } + if (info != NULL) { + free(info); + } + if (secret != NULL) { + OPENSSL_cleanse(secret, secret_len); + free(secret); + } + return err; +} + +void ss_ecdh_keypair_free(ss_ecdh_keypair_t *kp) { + if (kp == NULL) { + return; + } + EVP_PKEY_free(kp->pkey); + free(kp); +} + +void ss_ecdh_pubkey_free(ss_ecdh_pubkey_t *pub) { + if (pub == NULL) { + return; + } + EVP_PKEY_free(pub->pkey); + free(pub); +} diff --git a/crypto/src/ss_rsa.c b/crypto/src/ss_rsa.c new file mode 100644 index 0000000..b1c43cb --- /dev/null +++ b/crypto/src/ss_rsa.c @@ -0,0 +1,231 @@ +/* + * ss_rsa.c - RSA-OAEP (SHA-256) key management and encryption. + */ +#include "ss_rsa.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +struct ss_rsa_key { + EVP_PKEY *pkey; +}; + +ss_err_t ss_rsa_keygen(int bits, ss_rsa_key_t **out_key) { + if (out_key == NULL) { + return SS_ERR_INVALID_ARG; + } + if (bits < (int)SS_RSA_MIN_BITS || bits > (int)SS_RSA_MAX_BITS) { + return SS_ERR_INVALID_ARG; + } + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (ctx == NULL) { + return SS_ERR_OPENSSL; + } + if (EVP_PKEY_keygen_init(ctx) <= 0 || + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + EVP_PKEY *pkey = NULL; + if (EVP_PKEY_keygen(ctx, &pkey) <= 0 || pkey == NULL) { + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + EVP_PKEY_CTX_free(ctx); + + ss_rsa_key_t *key = (ss_rsa_key_t *)calloc(1, sizeof(*key)); + if (key == NULL) { + EVP_PKEY_free(pkey); + return SS_ERR_NOMEM; + } + key->pkey = pkey; + *out_key = key; + return SS_OK; +} + +static ss_err_t read_pem_key(const char *path, const char *passphrase, int want_private, + ss_rsa_key_t **out_key) { + if (path == NULL || out_key == NULL) { + return SS_ERR_INVALID_ARG; + } + BIO *bio = BIO_new_file(path, "rb"); + if (bio == NULL) { + return SS_ERR_IO; + } + EVP_PKEY *pkey = NULL; + if (want_private) { + pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, (void *)passphrase); + } else { + pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + } + BIO_free(bio); + if (pkey == NULL) { + /* Covers malformed PEM, wrong type, and wrong passphrase. */ + return SS_ERR_PARSE; + } + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) { + EVP_PKEY_free(pkey); + return SS_ERR_UNSUPPORTED; + } + ss_rsa_key_t *key = (ss_rsa_key_t *)calloc(1, sizeof(*key)); + if (key == NULL) { + EVP_PKEY_free(pkey); + return SS_ERR_NOMEM; + } + key->pkey = pkey; + *out_key = key; + return SS_OK; +} + +ss_err_t ss_rsa_read_pub(const char *path, ss_rsa_key_t **out_key) { + return read_pem_key(path, NULL, 0, out_key); +} + +ss_err_t ss_rsa_read_priv(const char *path, const char *passphrase, ss_rsa_key_t **out_key) { + return read_pem_key(path, passphrase, 1, out_key); +} + +ss_err_t ss_rsa_write_pub(const ss_rsa_key_t *key, const char *path) { + if (key == NULL || key->pkey == NULL || path == NULL) { + return SS_ERR_INVALID_ARG; + } + BIO *bio = BIO_new_file(path, "wb"); + if (bio == NULL) { + return SS_ERR_IO; + } + int rc = PEM_write_bio_PUBKEY(bio, key->pkey); + BIO_free(bio); + return rc == 1 ? SS_OK : SS_ERR_OPENSSL; +} + +ss_err_t ss_rsa_write_priv(const ss_rsa_key_t *key, const char *path, + const char *passphrase) { + if (key == NULL || key->pkey == NULL || path == NULL) { + return SS_ERR_INVALID_ARG; + } + BIO *bio = BIO_new_file(path, "wb"); + if (bio == NULL) { + return SS_ERR_IO; + } + const EVP_CIPHER *cipher = passphrase != NULL ? EVP_aes_256_cbc() : NULL; + int rc = PEM_write_bio_PKCS8PrivateKey(bio, key->pkey, cipher, NULL, 0, NULL, + (void *)passphrase); + BIO_free(bio); + return rc == 1 ? SS_OK : SS_ERR_OPENSSL; +} + +size_t ss_rsa_ciphertext_len(const ss_rsa_key_t *key) { + if (key == NULL || key->pkey == NULL) { + return 0; + } + return (size_t)EVP_PKEY_size(key->pkey); +} + +size_t ss_rsa_max_plaintext_len(const ss_rsa_key_t *key) { + size_t k = ss_rsa_ciphertext_len(key); + if (k == 0) { + return 0; + } + size_t hlen = (size_t)EVP_MD_size(EVP_sha256()); + /* OAEP: k - 2*hLen - 2 */ + return k > 2 * hlen + 2 ? k - 2 * hlen - 2 : 0; +} + +ss_err_t ss_rsa_encrypt(const ss_rsa_key_t *key, const uint8_t *in, size_t in_len, + uint8_t *out, size_t *out_len) { + if (key == NULL || key->pkey == NULL || in == NULL || in_len == 0 || out_len == NULL) { + return SS_ERR_INVALID_ARG; + } + if (in_len > ss_rsa_max_plaintext_len(key)) { + return SS_ERR_INVALID_ARG; + } + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key->pkey, NULL); + if (ctx == NULL) { + return SS_ERR_OPENSSL; + } + if (EVP_PKEY_encrypt_init(ctx) <= 0 || + EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0 || + EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha256()) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + size_t need = 0; + if (EVP_PKEY_encrypt(ctx, NULL, &need, in, in_len) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + if (out == NULL) { + *out_len = need; + EVP_PKEY_CTX_free(ctx); + return SS_OK; + } + if (*out_len < need) { + *out_len = need; + EVP_PKEY_CTX_free(ctx); + return SS_ERR_BUFFER_TOO_SMALL; + } + size_t actual = need; + if (EVP_PKEY_encrypt(ctx, out, &actual, in, in_len) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + *out_len = actual; + EVP_PKEY_CTX_free(ctx); + return SS_OK; +} + +ss_err_t ss_rsa_decrypt(const ss_rsa_key_t *key, const uint8_t *in, size_t in_len, + uint8_t *out, size_t *out_len) { + if (key == NULL || key->pkey == NULL || in == NULL || in_len == 0 || out_len == NULL) { + return SS_ERR_INVALID_ARG; + } + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key->pkey, NULL); + if (ctx == NULL) { + return SS_ERR_OPENSSL; + } + if (EVP_PKEY_decrypt_init(ctx) <= 0 || + EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0 || + EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha256()) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_OPENSSL; + } + size_t need = 0; + if (EVP_PKEY_decrypt(ctx, NULL, &need, in, in_len) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_DECRYPT; + } + if (out == NULL) { + *out_len = need; + EVP_PKEY_CTX_free(ctx); + return SS_OK; + } + if (*out_len < need) { + *out_len = need; + EVP_PKEY_CTX_free(ctx); + return SS_ERR_BUFFER_TOO_SMALL; + } + size_t actual = need; + if (EVP_PKEY_decrypt(ctx, out, &actual, in, in_len) <= 0) { + EVP_PKEY_CTX_free(ctx); + return SS_ERR_DECRYPT; + } + *out_len = actual; + EVP_PKEY_CTX_free(ctx); + return SS_OK; +} + +void ss_rsa_key_free(ss_rsa_key_t *key) { + if (key == NULL) { + return; + } + EVP_PKEY_free(key->pkey); + free(key); +} diff --git a/crypto/ss_crypto.pc.in b/crypto/ss_crypto.pc.in new file mode 100644 index 0000000..4d57572 --- /dev/null +++ b/crypto/ss_crypto.pc.in @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: ss_crypto +Description: ServSpy C/OpenSSL cryptography library (RSA-OAEP, ECDH, HKDF, AES-256-GCM) +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -lss_crypto +Libs.private: -lcrypto +Cflags: -I${includedir} diff --git a/crypto/tests/CMakeLists.txt b/crypto/tests/CMakeLists.txt new file mode 100644 index 0000000..2f72760 --- /dev/null +++ b/crypto/tests/CMakeLists.txt @@ -0,0 +1,20 @@ +# Test executables for ss_crypto. + +foreach(test_name IN ITEMS test_hkdf test_rsa test_ecdh) + add_executable(${test_name} ${test_name}.c) + target_link_libraries(${test_name} PRIVATE ss_crypto) + target_compile_options(${test_name} PRIVATE -Wall -Wextra) + add_test(NAME ${test_name} COMMAND ${test_name}) +endforeach() + +# Memory/UB sanity under ASan+UBSan when the compiler supports it. +if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + include(CheckCCompilerFlag) + check_c_compiler_flag("-fsanitize=address,undefined" SS_CRYPTO_HAS_SANITIZERS) + if(SS_CRYPTO_HAS_SANITIZERS) + foreach(test_name IN ITEMS test_hkdf test_rsa test_ecdh) + target_compile_options(${test_name} PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer) + target_link_options(${test_name} PRIVATE -fsanitize=address,undefined) + endforeach() + endif() +endif() diff --git a/crypto/tests/test_ecdh.c b/crypto/tests/test_ecdh.c new file mode 100644 index 0000000..ccc9398 --- /dev/null +++ b/crypto/tests/test_ecdh.c @@ -0,0 +1,235 @@ +/* ECDH + AES-256-GCM seal/open test suite. */ +#include "ss_ecdh.h" +#include "ss_rsa.h" + +#include +#include +#include + +#include "test_util.h" + +#define PRIV_A_FILE "test_ecdh_a_priv.pem" +#define PASSPHRASE "ecdh passphrase" +#define SALT "test salt" +#define INFO "test info" +#define AAD "authenticated but not encrypted" + +static const uint8_t kPayload[] = { + 0x00, 0x01, 0x02, 0xde, 0xad, 0xbe, 0xef, 0x00, 0xff, 0xfe, +}; + + +static void run_tests(void) { + ss_ecdh_keypair_t *a = NULL; + ss_ecdh_keypair_t *b = NULL; + CHECK_ERR(ss_ecdh_keypair_generate(SS_ECDH_CURVE_P256, &a), SS_OK); + CHECK_ERR(ss_ecdh_keypair_generate(SS_ECDH_CURVE_P256, &b), SS_OK); + CHECK(a != NULL && b != NULL); + + char *pem_a = NULL, *pem_b = NULL; + CHECK_ERR(ss_ecdh_pub_to_pem(a, &pem_a), SS_OK); + CHECK_ERR(ss_ecdh_pub_to_pem(b, &pem_b), SS_OK); + CHECK(pem_a != NULL && strstr(pem_a, "PUBLIC KEY") != NULL); + + ss_ecdh_pubkey_t *pub_a = NULL, *pub_b = NULL; + CHECK_ERR(ss_ecdh_pub_from_pem(pem_a, &pub_a), SS_OK); + CHECK_ERR(ss_ecdh_pub_from_pem(pem_b, &pub_b), SS_OK); + + uint8_t key_a[32], key_b[32]; + CHECK_ERR(ss_ecdh_derive_key(a, pub_b, (const uint8_t *)SALT, strlen(SALT), + (const uint8_t *)INFO, strlen(INFO), key_a, + sizeof(key_a)), SS_OK); + CHECK_ERR(ss_ecdh_derive_key(b, pub_a, (const uint8_t *)SALT, strlen(SALT), + (const uint8_t *)INFO, strlen(INFO), key_b, + sizeof(key_b)), SS_OK); + CHECK(memcmp(key_a, key_b, sizeof(key_a)) == 0); + + uint8_t *sealed = NULL; + size_t sealed_len = 0; + CHECK_ERR(ss_ecdh_seal(a, pub_b, (const uint8_t *)AAD, strlen(AAD), kPayload, + sizeof(kPayload), &sealed, &sealed_len), SS_OK); + CHECK(sealed_len == sizeof(kPayload) + SS_ECDH_SEAL_OVERHEAD); + + uint8_t *opened = NULL; + size_t opened_len = 0; + CHECK_ERR(ss_ecdh_open(b, pub_a, (const uint8_t *)AAD, strlen(AAD), sealed, + sealed_len, &opened, &opened_len), SS_OK); + CHECK(opened_len == sizeof(kPayload)); + CHECK(memcmp(opened, kPayload, sizeof(kPayload)) == 0); + free(opened); + free(sealed); + + /* Empty plaintext (AAD-only) */ + CHECK_ERR(ss_ecdh_seal(a, pub_b, NULL, 0, NULL, 0, &sealed, &sealed_len), SS_OK); + CHECK(sealed_len == SS_ECDH_SEAL_OVERHEAD); + CHECK_ERR(ss_ecdh_open(b, pub_a, NULL, 0, sealed, sealed_len, &opened, &opened_len), + SS_OK); + CHECK(opened_len == 0); + free(opened); + free(sealed); + + /* Tampering: ciphertext bit flip */ + uint8_t *sealed2 = NULL; + size_t sealed2_len = 0; + CHECK_ERR(ss_ecdh_seal(a, pub_b, NULL, 0, kPayload, sizeof(kPayload), &sealed2, + &sealed2_len), SS_OK); + sealed2[SS_ECDH_SEAL_OVERHEAD] ^= 0x01; + CHECK_ERR(ss_ecdh_open(b, pub_a, NULL, 0, sealed2, sealed2_len, &opened, &opened_len), + SS_ERR_AUTH_FAILED); + CHECK(opened == NULL); + free(sealed2); + + /* Tampering: AAD bit flip */ + uint8_t *aad_sealed = NULL; + size_t aad_sealed_len = 0; + CHECK_ERR(ss_ecdh_seal(a, pub_b, (const uint8_t *)AAD, strlen(AAD), kPayload, + sizeof(kPayload), &aad_sealed, &aad_sealed_len), SS_OK); + uint8_t bad_aad[] = "authenticated but NOT encrypted"; + CHECK_ERR(ss_ecdh_open(b, pub_a, bad_aad, sizeof(bad_aad) - 1, aad_sealed, + aad_sealed_len, &opened, &opened_len), SS_ERR_AUTH_FAILED); + CHECK(opened == NULL); + free(aad_sealed); + + /* Third-party with a different key pair fails to open */ + ss_ecdh_keypair_t *c = NULL; + CHECK_ERR(ss_ecdh_keypair_generate(SS_ECDH_CURVE_P256, &c), SS_OK); + char *pem_c = NULL; + CHECK_ERR(ss_ecdh_pub_to_pem(c, &pem_c), SS_OK); + ss_ecdh_pubkey_t *pub_c = NULL; + CHECK_ERR(ss_ecdh_pub_from_pem(pem_c, &pub_c), SS_OK); + sealed2 = NULL; + sealed2_len = 0; + CHECK_ERR(ss_ecdh_seal(a, pub_b, NULL, 0, kPayload, sizeof(kPayload), &sealed2, + &sealed2_len), SS_OK); + CHECK_ERR(ss_ecdh_open(c, pub_a, NULL, 0, sealed2, sealed2_len, &opened, &opened_len), + SS_ERR_AUTH_FAILED); + CHECK(opened == NULL); + free(sealed2); + /* Error: input too short */ + uint8_t tiny[10] = {0}; + CHECK_ERR(ss_ecdh_open(a, pub_b, NULL, 0, tiny, sizeof(tiny), &opened, &opened_len), + SS_ERR_INVALID_ARG); + CHECK(opened == NULL); + + /* Unsupported curve name. */ + ss_ecdh_keypair_t *bad = NULL; + CHECK_ERR(ss_ecdh_keypair_generate("secp256k1", &bad), SS_ERR_UNSUPPORTED); + CHECK(bad == NULL); + + /* A non-EC public key is rejected. */ + ss_rsa_key_t *rsa = NULL; + CHECK_ERR(ss_rsa_keygen(2048, &rsa), SS_OK); + CHECK_ERR(ss_rsa_write_pub(rsa, "test_rsa_for_ecdh.pem"), SS_OK); + FILE *fp = fopen("test_rsa_for_ecdh.pem", "rb"); + CHECK(fp != NULL); + char rsa_pem[4096]; + size_t got = fread(rsa_pem, 1, sizeof(rsa_pem) - 1, fp); + fclose(fp); + rsa_pem[got] = '\0'; + ss_ecdh_pubkey_t *not_ec = NULL; + CHECK_ERR(ss_ecdh_pub_from_pem(rsa_pem, ¬_ec), SS_ERR_UNSUPPORTED); + CHECK(not_ec == NULL); + remove("test_rsa_for_ecdh.pem"); + ss_rsa_key_free(rsa); + + /* Private key persistence with passphrase. */ + CHECK_ERR(ss_ecdh_keypair_write_priv(a, PRIV_A_FILE, PASSPHRASE), SS_OK); + ss_ecdh_keypair_t *a_loaded = NULL; + CHECK_ERR(ss_ecdh_keypair_read_priv(PRIV_A_FILE, "wrong", &a_loaded), SS_ERR_PARSE); + CHECK(a_loaded == NULL); + CHECK_ERR(ss_ecdh_keypair_read_priv(PRIV_A_FILE, PASSPHRASE, &a_loaded), SS_OK); + CHECK(a_loaded != NULL); + + uint8_t key_a_loaded[32]; + CHECK_ERR(ss_ecdh_derive_key(a_loaded, pub_b, (const uint8_t *)SALT, strlen(SALT), + (const uint8_t *)INFO, strlen(INFO), key_a_loaded, + sizeof(key_a_loaded)), SS_OK); + CHECK(memcmp(key_a_loaded, key_a, sizeof(key_a)) == 0); + + remove(PRIV_A_FILE); + ss_ecdh_keypair_free(a_loaded); + free(pem_c); + ss_ecdh_pubkey_free(pub_c); + ss_ecdh_keypair_free(c); + + /* P-384 roundtrip */ + ss_ecdh_keypair_t *a384 = NULL, *b384 = NULL; + CHECK_ERR(ss_ecdh_keypair_generate(SS_ECDH_CURVE_P384, &a384), SS_OK); + CHECK_ERR(ss_ecdh_keypair_generate(SS_ECDH_CURVE_P384, &b384), SS_OK); + char *pem_a384 = NULL; + char *pem_b384 = NULL; + CHECK_ERR(ss_ecdh_pub_to_pem(a384, &pem_a384), SS_OK); + CHECK_ERR(ss_ecdh_pub_to_pem(b384, &pem_b384), SS_OK); + ss_ecdh_pubkey_t *pub_b384 = NULL; + CHECK_ERR(ss_ecdh_pub_from_pem(pem_b, &pub_b384), SS_OK); /* P-256 parses, fails later */ + ss_ecdh_pubkey_t *pub_a384 = NULL; + CHECK_ERR(ss_ecdh_pub_from_pem(pem_a384, &pub_a384), SS_OK); + ss_ecdh_pubkey_t *pub_b384_real = NULL; + CHECK_ERR(ss_ecdh_pub_from_pem(pem_b384, &pub_b384_real), SS_OK); + + uint8_t *s384 = NULL; + size_t s384_len = 0; + uint8_t *cross_curve = NULL; + size_t cross_curve_len = 0; + CHECK_ERR(ss_ecdh_seal(a384, pub_b384, NULL, 0, kPayload, sizeof(kPayload), + &cross_curve, &cross_curve_len), SS_ERR_OPENSSL); + CHECK(cross_curve == NULL); + + CHECK_ERR(ss_ecdh_seal(a384, pub_b384_real, NULL, 0, kPayload, sizeof(kPayload), + &s384, &s384_len), SS_OK); + CHECK_ERR(ss_ecdh_open(b384, pub_a384, NULL, 0, s384, s384_len, &opened, + &opened_len), SS_OK); + CHECK(memcmp(opened, kPayload, sizeof(kPayload)) == 0); + free(opened); + opened = NULL; + free(s384); + ss_ecdh_pubkey_free(pub_b384_real); + free(pem_b384); + + /* P-521 roundtrip */ + ss_ecdh_keypair_t *a521 = NULL, *b521 = NULL; + CHECK_ERR(ss_ecdh_keypair_generate(SS_ECDH_CURVE_P521, &a521), SS_OK); + CHECK_ERR(ss_ecdh_keypair_generate(SS_ECDH_CURVE_P521, &b521), SS_OK); + char *pem_b521 = NULL; + CHECK_ERR(ss_ecdh_pub_to_pem(b521, &pem_b521), SS_OK); + ss_ecdh_pubkey_t *pub_b521 = NULL; + CHECK_ERR(ss_ecdh_pub_from_pem(pem_b521, &pub_b521), SS_OK); + + char *pem_a521 = NULL; + CHECK_ERR(ss_ecdh_pub_to_pem(a521, &pem_a521), SS_OK); + ss_ecdh_pubkey_t *pub_a521 = NULL; + CHECK_ERR(ss_ecdh_pub_from_pem(pem_a521, &pub_a521), SS_OK); + + uint8_t *s521 = NULL; + size_t s521_len = 0; + CHECK_ERR(ss_ecdh_seal(a521, pub_b521, NULL, 0, kPayload, sizeof(kPayload), + &s521, &s521_len), SS_OK); + CHECK_ERR(ss_ecdh_open(b521, pub_a521, NULL, 0, s521, s521_len, &opened, &opened_len), + SS_OK); + CHECK(memcmp(opened, kPayload, sizeof(kPayload)) == 0); + free(opened); + free(s521); + + free(pem_a521); + ss_ecdh_pubkey_free(pub_a521); + ss_ecdh_keypair_free(b521); + ss_ecdh_keypair_free(a521); + free(pem_b521); + ss_ecdh_pubkey_free(pub_b521); + + free(pem_a384); + ss_ecdh_pubkey_free(pub_a384); + ss_ecdh_pubkey_free(pub_b384); + ss_ecdh_keypair_free(b384); + ss_ecdh_keypair_free(a384); + + + free(pem_a); + free(pem_b); + ss_ecdh_pubkey_free(pub_a); + ss_ecdh_pubkey_free(pub_b); + ss_ecdh_keypair_free(b); + ss_ecdh_keypair_free(a); +} + +TEST_MAIN("test_ecdh") \ No newline at end of file diff --git a/crypto/tests/test_hkdf.c b/crypto/tests/test_hkdf.c new file mode 100644 index 0000000..b755fba --- /dev/null +++ b/crypto/tests/test_hkdf.c @@ -0,0 +1,93 @@ +/* HKDF-SHA256 tests: RFC 5869 Appendix A test vectors. */ +#include "ss_crypto.h" + +#include +#include +#include + +#include "test_util.h" + +/* Hex string -> bytes; returns byte count or -1 on error. */ +static int hex2bin(const char *hex, uint8_t *out, size_t out_cap) { + size_t n = strlen(hex); + if (n % 2 != 0 || n / 2 > out_cap) { + return -1; + } + for (size_t i = 0; i < n / 2; i++) { + unsigned int byte; + if (sscanf(hex + 2 * i, "%2x", &byte) != 1) { + return -1; + } + out[i] = (uint8_t)byte; + } + return (int)(n / 2); +} + +static void check_rfc5869_case(const char *ikm_hex, const char *salt_hex, + const char *info_hex, size_t out_len, + const char *okm_hex) { + uint8_t ikm[128], salt[128], info[128], okm[255 * 32]; + int ikm_n = hex2bin(ikm_hex, ikm, sizeof(ikm)); + int salt_n = salt_hex ? hex2bin(salt_hex, salt, sizeof(salt)) : 0; + int info_n = info_hex ? hex2bin(info_hex, info, sizeof(info)) : 0; + int okm_n = okm_hex ? hex2bin(okm_hex, okm, sizeof(okm)) : -1; + CHECK(ikm_n > 0); + CHECK(okm_n >= 0 && (size_t)okm_n == out_len); + + uint8_t out[255 * 32]; + ss_err_t err = ss_crypto_hkdf_sha256( + ikm, (size_t)ikm_n, salt_n > 0 ? salt : NULL, (size_t)salt_n, + info_n > 0 ? info : NULL, (size_t)info_n, out, out_len); + CHECK_ERR(err, SS_OK); + CHECK(memcmp(out, okm, out_len) == 0); +} + +static void run_tests(void) { + /* RFC 5869 A.1: basic test case with SHA-256. */ + check_rfc5869_case( + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", /* IKM 22B */ + "000102030405060708090a0b0c", /* salt 13B */ + "f0f1f2f3f4f5f6f7f8f9", /* info 10B */ + 42, + "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"); + + /* RFC 5869 A.2: longer inputs/outputs. */ + check_rfc5869_case( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f", + "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + 82, + "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c" + "59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71" + "cc30c58179ec3e87c14c01d5c1f3434f1d87"); + + /* RFC 5869 A.3: zero-length salt and info. */ + check_rfc5869_case( + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + NULL, NULL, 42, + "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d" + "9d201395faa4b61a96c8"); + + /* Argument validation. */ + uint8_t out[32]; + uint8_t ikm[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + CHECK_ERR(ss_crypto_hkdf_sha256(NULL, 8, NULL, 0, NULL, 0, out, 32), + SS_ERR_INVALID_ARG); /* NULL ikm */ + CHECK_ERR(ss_crypto_hkdf_sha256(ikm, 0, NULL, 0, NULL, 0, out, 32), + SS_ERR_INVALID_ARG); /* empty ikm */ + CHECK_ERR(ss_crypto_hkdf_sha256(ikm, 8, NULL, 0, NULL, 0, NULL, 32), + SS_ERR_INVALID_ARG); /* NULL out */ + CHECK_ERR(ss_crypto_hkdf_sha256(ikm, 8, NULL, 0, NULL, 0, out, 0), + SS_ERR_INVALID_ARG); /* zero length */ + CHECK_ERR(ss_crypto_hkdf_sha256(ikm, 8, NULL, 0, NULL, 0, out, + SS_CRYPTO_HKDF_SHA256_MAX_OUT + 1), + SS_ERR_INVALID_ARG); /* too long */ +} + +TEST_MAIN("test_hkdf") diff --git a/crypto/tests/test_rsa.c b/crypto/tests/test_rsa.c new file mode 100644 index 0000000..67c219a --- /dev/null +++ b/crypto/tests/test_rsa.c @@ -0,0 +1,114 @@ +/* RSA-OAEP test suite. */ +#include "ss_rsa.h" + +#include +#include +#include + +#include "test_util.h" + +#define PUB_FILE "test_rsa_pub.pem" +#define PRIV_FILE "test_rsa_priv.pem" +#define PRIV_FILE_PW "test_rsa_priv_enc.pem" +#define PASSPHRASE "correct horse battery staple" + +static void run_tests(void) { + ss_rsa_key_t *key = NULL; + CHECK_ERR(ss_rsa_keygen(2048, &key), SS_OK); + CHECK(key != NULL); + + /* Key size invariants: 2048-bit RSA -> 256-byte ciphertext. */ + CHECK(ss_rsa_ciphertext_len(key) == 256); + CHECK(ss_rsa_max_plaintext_len(key) == 256 - 2 * 32 - 2); + + /* Binary roundtrip, including embedded NULs and high bytes. */ + uint8_t msg[100]; + for (size_t i = 0; i < sizeof(msg); i++) { + msg[i] = (uint8_t)(i * 7 + 1); /* includes 0x00 at i=73 */ + } + size_t ct_len = 0; + CHECK_ERR(ss_rsa_encrypt(key, msg, sizeof(msg), NULL, &ct_len), SS_OK); + CHECK(ct_len == 256); + + uint8_t *ct = (uint8_t *)malloc(ct_len); + CHECK(ct != NULL); + CHECK_ERR(ss_rsa_encrypt(key, msg, sizeof(msg), ct, &ct_len), SS_OK); + + size_t pt_len = 0; + CHECK_ERR(ss_rsa_decrypt(key, ct, ct_len, NULL, &pt_len), SS_OK); + CHECK(pt_len >= sizeof(msg)); + uint8_t *pt = (uint8_t *)malloc(pt_len); + CHECK(pt != NULL); + CHECK_ERR(ss_rsa_decrypt(key, ct, ct_len, pt, &pt_len), SS_OK); + CHECK(pt_len == sizeof(msg)); + CHECK(memcmp(pt, msg, sizeof(msg)) == 0); + free(pt); + free(ct); + + /* Oversized plaintext is rejected up front. */ + size_t dummy = 0; + CHECK_ERR(ss_rsa_encrypt(key, msg, ss_rsa_max_plaintext_len(key) + 1, NULL, &dummy), + SS_ERR_INVALID_ARG); + + /* PEM persistence, plaintext private key. */ + CHECK_ERR(ss_rsa_write_pub(key, PUB_FILE), SS_OK); + CHECK_ERR(ss_rsa_write_priv(key, PRIV_FILE, NULL), SS_OK); + + ss_rsa_key_t *pub_only = NULL; + CHECK_ERR(ss_rsa_read_pub(PUB_FILE, &pub_only), SS_OK); + CHECK(pub_only != NULL); + + ss_rsa_key_t *priv_loaded = NULL; + CHECK_ERR(ss_rsa_read_priv(PRIV_FILE, NULL, &priv_loaded), SS_OK); + CHECK(priv_loaded != NULL); + + /* Public-key-only handle encrypts; decryption without the private + * half fails. */ + size_t ct2_len = 0; + CHECK_ERR(ss_rsa_encrypt(pub_only, msg, 16, NULL, &ct2_len), SS_OK); + uint8_t *ct2 = (uint8_t *)malloc(ct2_len); + CHECK(ct2 != NULL); + CHECK_ERR(ss_rsa_encrypt(pub_only, msg, 16, ct2, &ct2_len), SS_OK); + uint8_t *pt2 = (uint8_t *)malloc(ct2_len); + CHECK(pt2 != NULL); + size_t pt2_len = ct2_len; + CHECK_ERR(ss_rsa_decrypt(pub_only, ct2, ct2_len, pt2, &pt2_len), SS_ERR_DECRYPT); + + /* The loaded key can decrypt what the public key encrypted. */ + pt2_len = ct2_len; + CHECK_ERR(ss_rsa_decrypt(priv_loaded, ct2, ct2_len, pt2, &pt2_len), SS_OK); + CHECK(pt2_len == 16 && memcmp(pt2, msg, 16) == 0); + free(pt2); + free(ct2); + + /* Passphrase-protected private key. */ + CHECK_ERR(ss_rsa_write_priv(key, PRIV_FILE_PW, PASSPHRASE), SS_OK); + ss_rsa_key_t *pw_bad = NULL; + CHECK_ERR(ss_rsa_read_priv(PRIV_FILE_PW, "wrong", &pw_bad), SS_ERR_PARSE); + CHECK(pw_bad == NULL); + ss_rsa_key_t *pw_ok = NULL; + CHECK_ERR(ss_rsa_read_priv(PRIV_FILE_PW, PASSPHRASE, &pw_ok), SS_OK); + CHECK(pw_ok != NULL); + + /* Missing file -> I/O error; bad content -> parse error. */ + ss_rsa_key_t *missing = NULL; + CHECK_ERR(ss_rsa_read_pub("/nonexistent/pub.pem", &missing), SS_ERR_IO); + CHECK(missing == NULL); + + /* Buffer-too-small path. */ + size_t small = 10; + uint8_t small_buf[10]; + CHECK_ERR(ss_rsa_encrypt(key, msg, 16, small_buf, &small), SS_ERR_BUFFER_TOO_SMALL); + CHECK(small == 256); + ss_rsa_key_free(pw_ok); + ss_rsa_key_free(pw_bad); + ss_rsa_key_free(priv_loaded); + ss_rsa_key_free(pub_only); + ss_rsa_key_free(key); + + remove(PUB_FILE); + remove(PRIV_FILE); + remove(PRIV_FILE_PW); +} + +TEST_MAIN("test_rsa") diff --git a/crypto/tests/test_util.h b/crypto/tests/test_util.h new file mode 100644 index 0000000..86ece0e --- /dev/null +++ b/crypto/tests/test_util.h @@ -0,0 +1,45 @@ +/* Shared test harness: check macros with failure accounting. */ +#ifndef SS_CRYPTO_TEST_UTIL_H +#define SS_CRYPTO_TEST_UTIL_H + +#include +#include +#include + +static int g_checks = 0; +static int g_failures = 0; + +#define CHECK(cond) \ + do { \ + g_checks++; \ + if (!(cond)) { \ + g_failures++; \ + fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + } \ + } while (0) + +#define CHECK_ERR(err, expected) \ + do { \ + ss_err_t _e = (err); \ + g_checks++; \ + if (_e != (expected)) { \ + g_failures++; \ + fprintf(stderr, \ + "FAIL %s:%d: %s returned %s (%d), expected %s (%d)\n", \ + __FILE__, __LINE__, #err, ss_err_string(_e), (int)_e, \ + ss_err_string(expected), (int)(expected)); \ + } \ + } while (0) + +#define TEST_MAIN(name) \ + int main(void) { \ + run_tests(); \ + if (g_failures == 0) { \ + printf("%s: PASS (%d checks)\n", name, g_checks); \ + return 0; \ + } \ + printf("%s: FAIL (%d/%d checks failed)\n", name, g_failures, g_checks); \ + return 1; \ + } + +#endif /* SS_CRYPTO_TEST_UTIL_H */ diff --git a/docs/Crypto/Crypto.rst b/docs/Crypto/Crypto.rst new file mode 100644 index 0000000..00247da --- /dev/null +++ b/docs/Crypto/Crypto.rst @@ -0,0 +1,200 @@ +C/OpenSSL Crypto Module +======================== + +The ``crypto`` directory contains ServSpy's standalone C/OpenSSL +cryptography library. The library is independent from the Python runtime +and can be built either as part of the repository or as a separate CMake +project. + +The module provides: + +- RSA-OAEP encryption and decryption with SHA-256. +- ECDH key agreement on P-256, P-384, and P-521. +- HKDF-SHA256 session-key derivation. +- AES-256-GCM authenticated encryption for ECDH sessions. +- PEM key persistence and structured error reporting. + +The public headers are located in ``crypto/include``: + +- ``ss_crypto.h`` - common errors, OpenSSL diagnostics, and HKDF. +- ``ss_rsa.h`` - RSA key and encryption APIs. +- ``ss_ecdh.h`` - ECDH key agreement and seal/open APIs. + +Build +===== + +The module requires OpenSSL 1.1.1 or newer and CMake 3.16 or newer. +On Debian or Ubuntu, install the build dependencies with: + +.. code-block:: bash + + sudo apt-get update + sudo apt-get install -y build-essential cmake libssl-dev + +Build it from the repository root: + +.. code-block:: bash + + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + cmake --build build --parallel + ctest --test-dir build --output-on-failure + +The module can also be built independently: + +.. code-block:: bash + + cmake -S crypto -B crypto-build -DCMAKE_BUILD_TYPE=Release + cmake --build crypto-build --parallel + ctest --test-dir crypto-build --output-on-failure + +CMake install rules export the ``ss_crypto`` library, its public headers, +and a CMake package configuration. A pkg-config template is also provided +as ``crypto/ss_crypto.pc.in``. + +Common API +========== + +All public functions return ``ss_err_t``. ``SS_OK`` indicates success. +Use ``ss_err_string`` to convert an error code to readable text and +``ss_crypto_openssl_errors`` to retrieve the pending OpenSSL error queue. + +.. code-block:: c + + #include "ss_crypto.h" + + ss_err_t error = ss_crypto_hkdf_sha256( + ikm, ikm_len, + salt, salt_len, + info, info_len, + session_key, 32); + + if (error != SS_OK) { + fprintf(stderr, "crypto error: %s\n", ss_err_string(error)); + } + +The library uses caller-provided output buffers for fixed-size results. +Functions that return allocated buffers document that the caller must +release them with ``free``. Opaque key handles must be released with their +corresponding ``*_free`` function. + +RSA API +======= + +RSA keys are generated with ``ss_rsa_keygen``. The implementation uses +RSA-OAEP with SHA-256 for encryption and decryption. Key sizes from 2048 to +16384 bits are accepted; 2048, 3072, or 4096 bits are recommended. + +.. code-block:: c + + ss_rsa_key_t *key = NULL; + ss_err_t error = ss_rsa_keygen(3072, &key); + if (error != SS_OK) { + return error; + } + + size_t ciphertext_len = 0; + error = ss_rsa_encrypt(key, plaintext, plaintext_len, + NULL, &ciphertext_len); + if (error == SS_OK) { + uint8_t *ciphertext = malloc(ciphertext_len); + error = ss_rsa_encrypt(key, plaintext, plaintext_len, + ciphertext, &ciphertext_len); + free(ciphertext); + } + + ss_rsa_key_free(key); + +The first encryption call with ``out == NULL`` queries the required output +size. RSA encryption is binary-safe and accepts an explicit input length. +The maximum plaintext size is returned by ``ss_rsa_max_plaintext_len``. + +Public and private keys can be stored as PEM files: + +.. code-block:: c + + ss_rsa_write_pub(key, "server-public.pem"); + ss_rsa_write_priv(key, "server-private.pem", "passphrase"); + + ss_rsa_read_pub("server-public.pem", &public_key); + ss_rsa_read_priv("server-private.pem", "passphrase", &private_key); + +ECDH API +======== + +ECDH key pairs are created with ``ss_ecdh_keypair_generate``. The supported +curve names are: + +- ``SS_ECDH_CURVE_P256`` +- ``SS_ECDH_CURVE_P384`` +- ``SS_ECDH_CURVE_P521`` + +Public keys are exchanged as PEM SubjectPublicKeyInfo strings. Parsed peer +keys are checked before use. + +.. code-block:: c + + ss_ecdh_keypair_t *local = NULL; + ss_ecdh_pubkey_t *peer = NULL; + char *public_pem = NULL; + + ss_ecdh_keypair_generate(SS_ECDH_CURVE_P256, &local); + ss_ecdh_pub_to_pem(local, &public_pem); + ss_ecdh_pub_from_pem(peer_pem, &peer); + + uint8_t session_key[32]; + ss_ecdh_derive_key(local, peer, + salt, salt_len, + info, info_len, + session_key, sizeof(session_key)); + + free(public_pem); + ss_ecdh_pubkey_free(peer); + ss_ecdh_keypair_free(local); + +The higher-level ``ss_ecdh_seal`` and ``ss_ecdh_open`` APIs are recommended +for application payloads. They derive an AES-256-GCM key with HKDF-SHA256, +include a random salt and IV, and authenticate optional AAD. + +The ``ss_ecdh_seal`` output format is: + +.. code-block:: text + + salt(16 bytes) | iv(12 bytes) | ciphertext(N bytes) | tag(16 bytes) + +The fixed overhead is ``SS_ECDH_SEAL_OVERHEAD`` (44 bytes). Empty plaintext +is allowed. A failed tag check returns ``SS_ERR_AUTH_FAILED`` and no +plaintext is returned. + +Security Notes +============== + +ECDH provides key agreement, but it does not authenticate public-key +ownership by itself. Public keys must be exchanged over an authenticated +channel or verified with an external signature/certificate mechanism. + +Private key files may be protected with a passphrase. Applications should +restrict their file permissions and avoid logging passphrases, private keys, +or plaintext session keys. + +Error Handling +============== + +The main error codes are: + +- ``SS_ERR_INVALID_ARG`` - invalid pointer or length. +- ``SS_ERR_NOMEM`` - allocation failure. +- ``SS_ERR_OPENSSL`` - OpenSSL operation failure. +- ``SS_ERR_IO`` - file operation failure. +- ``SS_ERR_PARSE`` - malformed PEM/DER input or wrong private-key passphrase. +- ``SS_ERR_BUFFER_TOO_SMALL`` - caller output buffer is insufficient. +- ``SS_ERR_AUTH_FAILED`` - AES-GCM authentication failed. +- ``SS_ERR_UNSUPPORTED`` - unsupported curve or key type. +- ``SS_ERR_DECRYPT`` - RSA decryption failed. + +For detailed OpenSSL diagnostics: + +.. code-block:: c + + char details[4096]; + ss_crypto_openssl_errors(details, sizeof(details)); + fprintf(stderr, "%s\n", details); diff --git a/docs/index.rst b/docs/index.rst index a64ddb2..b7fa068 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,3 +21,4 @@ documentation for details. File_Transfer/File_Transfer Port_Allocation/Port_Allocation Instance_Setup/Instance_Setup + Crypto/Crypto diff --git a/src/encrypt_api/encrypt_ECDH.c b/src/encrypt_api/encrypt_ECDH.c deleted file mode 100644 index 6313b4b..0000000 --- a/src/encrypt_api/encrypt_ECDH.c +++ /dev/null @@ -1,357 +0,0 @@ -#include "include/encrypt_ECDH.h" - -BIGNUM *generate_private_key() -{ - const char *rt_err = NULL; - EC_GROUP *group = EC_GROUP_new_by_curve_name(EC_name); - if (!group) - { - rt_err = "Failed to create EC_GROUP"; - goto err; - } - BIGNUM *priv_key = BN_new(); - const BIGNUM *order = EC_GROUP_get0_order(group); - if (!order) - { - rt_err = "Failed to create order"; - goto err; - } - BN_rand_range(priv_key, order); - if (!priv_key) - { - rt_err = "Failed to create priv_key"; - goto err; - } - EC_GROUP_free(group); - return priv_key; - -err: - if (group) - EC_GROUP_free(group); - printf("[ERROR]: %s\n", rt_err); - return NULL; -} - -EC_POINT *generate_public_key(const BIGNUM *priv_key) -{ - const char *rt_err = NULL; - EC_GROUP *group = EC_GROUP_new_by_curve_name(EC_name); - if (!group) - { - rt_err = "Failed to create EC_GROUP"; - goto err; - } - EC_POINT *pub_key = EC_POINT_new(group); - EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, NULL); - if (!pub_key) - { - rt_err = "Failed to create pub_key"; - goto err; - } - EC_GROUP_free(group); - return pub_key; - -err: - if (group) - EC_GROUP_free(group); - printf("[ERROR]: %s\n", rt_err); - return NULL; -} - -EC_POINT *compute_shared_secret(const BIGNUM *priv_key, const EC_POINT *pub_key) -{ - const char *rt_err = NULL; - EC_GROUP *group = EC_GROUP_new_by_curve_name(EC_name); - if (!group) - { - rt_err = "Failed to create EC_GROUP"; - goto err; - } - EC_POINT *shared_secret = EC_POINT_new(group); - EC_POINT_mul(group, shared_secret, NULL, pub_key, priv_key, NULL); - if (!shared_secret) - { - rt_err = "Failed to create shared_secret"; - goto err; - } - EC_GROUP_free(group); - return shared_secret; - -err: - if (group) - EC_GROUP_free(group); - printf("[ERROR]: %s\n", rt_err); - return NULL; -} - -unsigned char *derive_key_from_shared_secret(const EC_POINT *shared_secret, size_t *key_len) -{ - const char *rt_err = NULL; - EC_GROUP *group = EC_GROUP_new_by_curve_name(EC_name); - if (!group) - { - rt_err = "Failed to create EC_GROUP"; - goto err; - } - - BIGNUM *x = BN_new(); - if (!x) - { - rt_err = "Failed to create BIGNUM for x coordinate"; - goto err; - } - - if (!EC_POINT_get_affine_coordinates(group, shared_secret, x, NULL, NULL)) - { - rt_err = "Failed to get affine coordinates"; - goto err; - } - - int bn_size = BN_num_bytes(x); - unsigned char *key_material = (unsigned char *)malloc(bn_size); - if (!key_material) - { - rt_err = "Failed to allocate memory for key material"; - goto err; - } - - BN_bn2bin(x, key_material); - - EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); - if (!mdctx) - { - rt_err = "Failed to create message digest context"; - goto err; - } - - unsigned char *derived_key = (unsigned char *)malloc(EVP_MAX_MD_SIZE); - if (!derived_key) - { - rt_err = "Failed to allocate memory for derived key"; - goto err; - } - - unsigned int digest_len; - if (!EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) || - !EVP_DigestUpdate(mdctx, key_material, bn_size) || - !EVP_DigestFinal_ex(mdctx, derived_key, &digest_len)) - { - rt_err = "Failed to derive key"; - goto err; - } - - *key_len = digest_len; - - EVP_MD_CTX_free(mdctx); - BN_free(x); - EC_GROUP_free(group); - free(key_material); - - return derived_key; - -err: - if (group) - EC_GROUP_free(group); - if (x) - BN_free(x); - printf("[ERROR]: %s\n", rt_err); - return NULL; -} - -// ... existing code ... - -unsigned char *encrypt_ecdh(const unsigned char *plaintext, size_t plaintext_len, - const EC_POINT *shared_secret, size_t *ciphertext_len) -{ - const char *rt_err = NULL; - EVP_CIPHER_CTX *ctx = NULL; - unsigned char *ciphertext = NULL; - - if (!plaintext || plaintext_len == 0 || !shared_secret || !ciphertext_len) - { - rt_err = "Invalid input parameters"; - goto err; - } - - if (plaintext_len > SIZE_MAX - 28) - { - rt_err = "Plaintext too large"; - goto err; - } - - size_t key_len; - unsigned char *key = derive_key_from_shared_secret(shared_secret, &key_len); - if (!key) - { - rt_err = "Failed to derive key from shared secret"; - goto err; - } - - unsigned char iv[12]; - if (!RAND_bytes(iv, sizeof(iv))) - { - rt_err = "Failed to generate IV"; - goto err; - } - - size_t total_len = sizeof(iv) + plaintext_len + 16; - ciphertext = (unsigned char *)malloc(total_len); - if (!ciphertext) - { - rt_err = "Failed to allocate memory for ciphertext"; - goto err; - } - - memcpy(ciphertext, iv, sizeof(iv)); - - ctx = EVP_CIPHER_CTX_new(); - if (!ctx) - { - rt_err = "Failed to create cipher context"; - goto err; - } - - if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv)) - { - rt_err = "Failed to initialize encryption"; - goto err; - } - - int len; - int ciphertext_part_len; - - if (!EVP_EncryptUpdate(ctx, ciphertext + sizeof(iv), &len, plaintext, plaintext_len)) - { - rt_err = "Failed to encrypt data"; - goto err; - } - ciphertext_part_len = len; - - if (!EVP_EncryptFinal_ex(ctx, ciphertext + sizeof(iv) + len, &len)) - { - rt_err = "Failed to finalize encryption"; - goto err; - } - ciphertext_part_len += len; - - if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, ciphertext + sizeof(iv) + ciphertext_part_len)) - { - rt_err = "Failed to get authentication tag"; - goto err; - } - - *ciphertext_len = sizeof(iv) + ciphertext_part_len + 16; - - EVP_CIPHER_CTX_free(ctx); - free(key); - - return ciphertext; - -err: - if (ctx) - EVP_CIPHER_CTX_free(ctx); - if (key) - free(key); - if (ciphertext) - free(ciphertext); - printf("[ERROR]: %s\n", rt_err); - return NULL; -} - -unsigned char *decrypt_ecdh(const unsigned char *ciphertext, size_t ciphertext_len, - const EC_POINT *shared_secret, size_t *plaintext_len) -{ - const char *rt_err = NULL; - EVP_CIPHER_CTX *ctx = NULL; - unsigned char *plaintext = NULL; - - if (!ciphertext || !shared_secret || !plaintext_len) - { - rt_err = "Invalid input parameters"; - goto err; - } - - if (ciphertext_len < 28) - { - rt_err = "Ciphertext too short"; - goto err; - } - - size_t key_len; - unsigned char *key = derive_key_from_shared_secret(shared_secret, &key_len); - if (!key) - { - rt_err = "Failed to derive key from shared secret"; - goto err; - } - - unsigned char iv[12]; - memcpy(iv, ciphertext, sizeof(iv)); - - size_t data_len = ciphertext_len - sizeof(iv) - 16; - if (data_len > SIZE_MAX - 1) - { - rt_err = "Ciphertext too large"; - goto err; - } - - plaintext = (unsigned char *)malloc(data_len + 1); - if (!plaintext) - { - rt_err = "Failed to allocate memory for plaintext"; - goto err; - } - - ctx = EVP_CIPHER_CTX_new(); - if (!ctx) - { - rt_err = "Failed to create cipher context"; - goto err; - } - - if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv)) - { - rt_err = "Failed to initialize decryption"; - goto err; - } - - int len; - int plaintext_part_len; - - if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext + sizeof(iv), data_len)) - { - rt_err = "Failed to decrypt data"; - goto err; - } - plaintext_part_len = len; - - if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void *)(ciphertext + sizeof(iv) + data_len))) - { - rt_err = "Failed to set authentication tag"; - goto err; - } - - if (!EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) - { - rt_err = "Authentication failed or decryption error"; - goto err; - } - plaintext_part_len += len; - - *plaintext_len = plaintext_part_len; - - EVP_CIPHER_CTX_free(ctx); - free(key); - - return plaintext; - -err: - if (ctx) - EVP_CIPHER_CTX_free(ctx); - if (key) - free(key); - if (plaintext) - free(plaintext); - printf("[ERROR]: %s\n", rt_err); - return NULL; -} diff --git a/src/encrypt_api/encrypt_RSA.c b/src/encrypt_api/encrypt_RSA.c deleted file mode 100644 index c89978c..0000000 --- a/src/encrypt_api/encrypt_RSA.c +++ /dev/null @@ -1,196 +0,0 @@ -#include "include/encrypt_RSA.h" - -EVP_PKEY *RSA_generate_keys(EVP_PKEY *rtrn_key) -{ - const char *rt_err = NULL; - EVP_PKEY_CTX *ctx = NULL; - EVP_PKEY *pkey = NULL; - ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); - if (!ctx) - { - rt_err = "EVP_PKEY_CTX_new_id failed"; - goto err; - } - if (EVP_PKEY_keygen_init(ctx) <= 0) - { - rt_err = "EVP_PKEY_keygen_init failed"; - goto err; - } - if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) <= 0) - { - rt_err = "EVP_PKEY_CTX_set_rsa_keygen_bits failed"; - goto err; - } - if (EVP_PKEY_keygen(ctx, &pkey) <= 0) - { - rt_err = "EVP_PKEY_keygen failed"; - goto err; - } - else if (pkey) - { - rtrn_key = pkey; - EVP_PKEY_CTX_free(ctx); - return rtrn_key; - } - -err: - if (ctx) - EVP_PKEY_CTX_free(ctx); - if (pkey) - return pkey; - printf("[ERROR]: %s\n", rt_err); - return NULL; -} - -int RSA_write(EVP_PKEY *RSA_key, const char *pubkey_file, const char *privkey_file) -{ - const char *rt_err = NULL; - FILE *fp = NULL; - if ((fp = fopen(pubkey_file, "wb")) == NULL) - { - rt_err = "Failed to open public key file for writing."; - goto err; - } - PEM_write_PUBKEY(fp, RSA_key); - fclose(fp); - if ((fp = fopen(privkey_file, "wb")) == NULL) - { - rt_err = "Failed to open private key file for writing."; - goto err; - } - PEM_write_PrivateKey(fp, RSA_key, NULL, NULL, 0, NULL, NULL); - fclose(fp); - return 0; -err: - printf("[ERROR]: %s\n", rt_err); - fclose(fp); - return -1; -} - -EVP_PKEY *RSA_read(const char *pubkey_file, const char *privkey_file) -{ - const char *rt_err = NULL; - FILE *fp = NULL; - EVP_PKEY *RSA_key = NULL; - if ((fp = fopen(pubkey_file, "rb")) == NULL) - { - printf("Failed to open public key file for reading.\n"); - goto err; - } - RSA_key = PEM_read_PUBKEY(fp, NULL, NULL, NULL); - fclose(fp); - if (!RSA_key) - { - printf("Failed to read public key from file.\n"); - goto err; - } - if ((fp = fopen(privkey_file, "rb")) == NULL) - { - printf("Failed to open private key file for reading.\n"); - goto err; - } - RSA_key = PEM_read_PrivateKey(fp, NULL, NULL, NULL); - fclose(fp); - if (!RSA_key) - { - printf("Failed to read private key from file.\n"); - goto err; - } - return RSA_key; -err: - if (fp) fclose(fp); - printf("[ERROR]: %s\n", rt_err); - return NULL; -} - -unsigned char *RSA_pub_encrypt(EVP_PKEY *RSA_key, const unsigned char *msg, size_t *encrypted_msg_length) -{ - const char *rt_err = NULL; - EVP_PKEY_CTX *ctx = NULL; - unsigned char *encrypted_msg = NULL; - size_t outlen; - - ctx = EVP_PKEY_CTX_new(RSA_key, NULL); - if (!ctx) - { - rt_err = "Failed to create EVP_PKEY_CTX."; - goto err; - } - - if (EVP_PKEY_encrypt_init(ctx) <= 0) - { - rt_err = "Failed to initialize EVP_PKEY_CTX for encryption."; - goto err; - } - if (EVP_PKEY_encrypt(ctx, NULL, &outlen, msg, strlen((const char *)msg)) <= 0) - { - rt_err = "Failed to get encrypted message length."; - goto err; - } - encrypted_msg = (unsigned char *)malloc(outlen); - if (!encrypted_msg) - { - rt_err = "Failed to allocate memory for encrypted message."; - goto err; - } - if (EVP_PKEY_encrypt(ctx, encrypted_msg, &outlen, msg, strlen((const char *)msg)) <= 0) - { - rt_err = "Failed to encrypt message."; - goto err; - } - - *encrypted_msg_length = outlen; - EVP_PKEY_CTX_free(ctx); - - return encrypted_msg; - -err: - printf("[ERROR]: %s\n", rt_err); - if(ctx) EVP_PKEY_CTX_free(ctx); - if(encrypted_msg) free(encrypted_msg); - return NULL; -} - -unsigned char *RSA_priv_decrypt(EVP_PKEY *RSA_key, const unsigned char *encrypted_msg, size_t encrypted_msg_length, size_t *decrypted_msg_length) -{ - const char *rt_err = NULL; - EVP_PKEY_CTX *ctx = NULL; - unsigned char *decrypted_msg = NULL; - size_t outlen; - ctx = EVP_PKEY_CTX_new(RSA_key, NULL); - if (!ctx) - { - rt_err = "Failed to create EVP_PKEY_CTX."; - goto err; - } - if (EVP_PKEY_decrypt_init(ctx) <= 0) - { - rt_err = "Failed to initialize EVP_PKEY_CTX for decryption."; - goto err; - } - if (EVP_PKEY_decrypt(ctx, NULL, &outlen, encrypted_msg, encrypted_msg_length) <= 0) - { - rt_err = "Failed to get decrypted message length."; - goto err; - } - decrypted_msg = (unsigned char *)malloc(outlen); - if (!decrypted_msg) - { - rt_err = "Failed to allocate memory for decrypted message."; - goto err; - } - if (EVP_PKEY_decrypt(ctx, decrypted_msg, &outlen, encrypted_msg, encrypted_msg_length) <= 0) - { - rt_err = "Failed to decrypt message."; - goto err; - } - *decrypted_msg_length = outlen; - EVP_PKEY_CTX_free(ctx); - return decrypted_msg; - -err: - printf("[ERROR]: %s\n", rt_err); - if(ctx) EVP_PKEY_CTX_free(ctx); - if(decrypted_msg) free(decrypted_msg); - return NULL; -} diff --git a/src/encrypt_api/encrypt_manage.c b/src/encrypt_api/encrypt_manage.c deleted file mode 100644 index e69de29..0000000 diff --git a/src/include/encrypt_ECDH.h b/src/include/encrypt_ECDH.h deleted file mode 100644 index 5209dcf..0000000 --- a/src/include/encrypt_ECDH.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ENCRYPT_ECDH_H -#define ENCRYPT_ECDH_H - -#include "encrypt_manage.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -BIGNUM *generate_private_key(); - -EC_POINT *generate_public_key(const BIGNUM *priv_key); - -EC_POINT *compute_shared_secret(const BIGNUM *priv_key, const EC_POINT *pub_key); - -unsigned char *derive_key_from_shared_secret(const EC_POINT *shared_secret, size_t *key_len); - -unsigned char *encrypt_ecdh(const unsigned char *plaintext, size_t plaintext_len, - const EC_POINT *shared_secret, size_t *ciphertext_len); - -unsigned char *decrypt_ecdh(const unsigned char *ciphertext, size_t ciphertext_len, - const EC_POINT *shared_secret, size_t *plaintext_len); - -#endif /* ENCRYPT_ECDH_H */ \ No newline at end of file diff --git a/src/include/encrypt_RSA.h b/src/include/encrypt_RSA.h deleted file mode 100644 index 7f3e40e..0000000 --- a/src/include/encrypt_RSA.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef ENCRYPT_RSA_H -#define ENCRYPT_RSA_H - -#include -#include -#include -#include -#include -#include - -EVP_PKEY *RSA_generate_keys(EVP_PKEY *rtrn_key); - -int RSA_write(EVP_PKEY *RSA_key, const char *pubkey_file, const char *privkey_file); - -EVP_PKEY *RSA_read(const char *pubkey_file, const char *privkey_file); - -unsigned char *RSA_pub_encrypt(EVP_PKEY *RSA_key, const unsigned char *msg, size_t *encrypted_msg_length); - -unsigned char *RSA_priv_decrypt(EVP_PKEY *RSA_key, const unsigned char *encrypted_msg, - size_t encrypted_msg_length, size_t *decrypted_msg_length); - -#endif /* ENCRYPT_RSA_H */ - - - - - diff --git a/src/include/encrypt_manage.h b/src/include/encrypt_manage.h deleted file mode 100644 index e2e5f93..0000000 --- a/src/include/encrypt_manage.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef ENCRYPT_MANAGE_H -#define ENCRYPT_MANAGE_H - -#define EC_name NID_X9_62_prime256v1 - -#endif /* ENCRYPT_MANAGE_H */ \ No newline at end of file diff --git a/test/test_encrypt_ECDH.c b/test/test_encrypt_ECDH.c deleted file mode 100644 index d8de0bb..0000000 --- a/test/test_encrypt_ECDH.c +++ /dev/null @@ -1,131 +0,0 @@ -#include "../src/include/encrypt_ECDH.h" -#include -#include - -int main() -{ - printf("=== ECDH Encrypt Decrypt Test ===\n"); - - printf("Generate A side private key...\n"); - BIGNUM *private_key_A = generate_private_key(); - if (!private_key_A) - { - printf("Failed to generate private key A\n"); - return 1; - } - - EC_POINT *public_key_A = generate_public_key(private_key_A); - if (!public_key_A) - { - printf("Failed to generate public key A\n"); - BN_free(private_key_A); - return 1; - } - printf("Successfully generate A side private key and public key\n"); - - printf("Generate B side private key...\n"); - BIGNUM *private_key_B = generate_private_key(); - if (!private_key_B) - { - printf("Failed to generate private key B\n"); - BN_free(private_key_A); - EC_POINT_free(public_key_A); - return 1; - } - - EC_POINT *public_key_B = generate_public_key(private_key_B); - if (!public_key_B) - { - printf("Failed to generate public key B\n"); - BN_free(private_key_A); - BN_free(private_key_B); - EC_POINT_free(public_key_A); - return 1; - } - printf("Successfully generate B side private key and public key\n"); - - printf("Compute shared secret...\n"); - EC_POINT *shared_secret_A = compute_shared_secret(private_key_A, public_key_B); - EC_POINT *shared_secret_B = compute_shared_secret(private_key_B, public_key_A); - - if (!shared_secret_A || !shared_secret_B) - { - printf("Failed to compute shared secret\n"); - } - else - { - printf("Successfully compute shared secret\n"); - - printf("Test key derivation...\n"); - size_t key_len_A, key_len_B; - unsigned char *key_A = derive_key_from_shared_secret(shared_secret_A, &key_len_A); - unsigned char *key_B = derive_key_from_shared_secret(shared_secret_B, &key_len_B); - - if (key_A && key_B && key_len_A == key_len_B && - memcmp(key_A, key_B, key_len_A) == 0) - { - printf("Successfully derive keys from shared secret, keys match\n"); - - printf("Test encrypt decrypt function...\n"); - const char *test_message = "Hello! This is a test message."; - size_t message_len = strlen(test_message) + 1; - - printf("Original message: %s\n", test_message); - - size_t ciphertext_len; - unsigned char *ciphertext = encrypt_ecdh((const unsigned char *)test_message, - message_len, shared_secret_A, &ciphertext_len); - - if (ciphertext) - { - printf("Successfully encrypt message, ciphertext length: %zu bytes\n", ciphertext_len); - - size_t decrypted_len; - unsigned char *decrypted = decrypt_ecdh(ciphertext, ciphertext_len, - shared_secret_B, &decrypted_len); - - if (decrypted && decrypted_len == message_len && - memcmp(decrypted, test_message, message_len) == 0) - { - printf("Successfully decrypt message: %s\n", (char *)decrypted); - printf("Encrypt decrypt test passed!\n"); - free(decrypted); - } - else - { - printf("Failed to decrypt message\n"); - } - - free(ciphertext); - } - else - { - printf("Failed to encrypt message\n"); - } - - free(key_A); - free(key_B); - } - else - { - printf("Failed to derive keys from shared secret or keys do not match\n"); - if (key_A) - free(key_A); - if (key_B) - free(key_B); - } - } - - printf("Clean up resources...\n"); - if (shared_secret_A) - EC_POINT_free(shared_secret_A); - if (shared_secret_B) - EC_POINT_free(shared_secret_B); - EC_POINT_free(public_key_A); - EC_POINT_free(public_key_B); - BN_free(private_key_A); - BN_free(private_key_B); - - printf("=== Test completed ===\n"); - return 0; -} diff --git a/test/test_encrypt_RSA.c b/test/test_encrypt_RSA.c deleted file mode 100644 index 99c1287..0000000 --- a/test/test_encrypt_RSA.c +++ /dev/null @@ -1,102 +0,0 @@ -#include -#include -#include -#include "../src/include/encrypt_RSA.h" - -int main() -{ - EVP_PKEY *key = NULL; - key = RSA_generate_keys(key); - if (key) - { - printf("RSA key pair generated successfully.\n"); - } - else - { - printf("Failed to generate RSA key pair.\n"); - } - - if (RSA_write(key, "pubkey.pem", "privkey.pem") != 0) - { - printf("Failed to write RSA key pair to files.\n"); - } - else - { - printf("RSA key pair written to files successfully.\n"); - } - - EVP_PKEY_free(key); - key = RSA_read("pubkey.pem", "privkey.pem"); - if (key) - { - printf("RSA key pair read from files successfully.\n"); - } - else - { - printf("Failed to read RSA key pair from files.\n"); - } - unsigned char msg[2048]; - unsigned char *encrypted_msg = NULL; - unsigned char *decrypted_msg = NULL; - - printf("Enter message to encrypt (max 245 characters for 2048-bit RSA): "); - scanf("%244s", msg); // 限制输入长度为244字符,为安全起见 - - // 检查消息长度 - if (strlen((char *)msg) > 245) - { - printf("Error: Message too long! Maximum length for 2048-bit RSA is 245 characters.\n"); - printf("Your message length: %lu characters\n", strlen((char *)msg)); - EVP_PKEY_free(key); - return 1; - } - - printf("Original msg: %s\n", msg); - printf("Message length: %lu characters\n", strlen((char *)msg)); - - size_t encrypted_msg_length = 0; - encrypted_msg = RSA_pub_encrypt(key, msg, &encrypted_msg_length); - - if (encrypted_msg) - { - printf("RSA message encrypted successfully.\n"); - printf("Encrypted msg length: %ld bytes\n", encrypted_msg_length); - printf("Encrypted msg (hex): "); - for (size_t i = 0; i < encrypted_msg_length; i++) - { - printf("%02x", encrypted_msg[i]); - } - printf("\n"); - } - else - { - printf("Failed to encrypt RSA message.\n"); - } - - size_t decrypted_msg_length = 0; - decrypted_msg = RSA_priv_decrypt(key, encrypted_msg, encrypted_msg_length, &decrypted_msg_length); - - if (decrypted_msg) - { - printf("RSA message decrypted successfully.\n"); - decrypted_msg[decrypted_msg_length] = '\0'; - printf("Decrypted msg: %s\n", decrypted_msg); - printf("Decrypted msg length: %ld bytes\n", decrypted_msg_length); - free(decrypted_msg); - } - else if (decrypted_msg_length == 0) - { - printf("Decrypted msg is empty.\n"); - } - else - { - printf("Failed to decrypt RSA message.\n"); - } - if (encrypted_msg) - { - free(encrypted_msg); - } - - EVP_PKEY_free(key); - return 0; -} \ No newline at end of file