You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

531 lines
25 KiB

/* secure_channel.c - Secure Channel library using X25519 + AES-128-CCM (OpenSSL) */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define OPENSSL_API_COMPAT 0x10100000L
#include "secure_channel.h"
#include "../lib/debug_config.h"
#include "../lib/platform_compat.h"
#include <string.h>
#include <stddef.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdio.h>
#include <fcntl.h>
#include "crc32.h"
#include "../lib/sha256.h"
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/err.h>
#include "../lib/platform_compat.h"
static uint8_t sc_urandom_seed[8] = {0};
static int sc_urandom_initialized = 0;
static void sc_init_random_seed(void)
{
if (random_bytes(sc_urandom_seed, 8) == 0) {
sc_urandom_initialized = 1;
}
}
// Конвертация hex строки в бинарный формат
static int hex_to_binary(const char *hex_str, uint8_t *binary, size_t binary_len) {
if (!hex_str || !binary || strlen(hex_str) != binary_len * 2) return -1;
for (size_t i = 0; i < binary_len; i++) {
unsigned int byte;
if (sscanf(hex_str + i * 2, "%2x", &byte) != 1) return -1;
binary[i] = (uint8_t)byte;
}
return 0;
}
sc_status_t sc_init_ctx(sc_context_t *ctx, struct SC_MYKEYS *mykeys) {
ctx->pk = mykeys;
ctx->initialized = 1;
ctx->peer_key_set = 0;
ctx->session_ready = 0;
ctx->tx_counter = 0;
ctx->rx_counter = 0;
return SC_OK;
}
// Common helper functions for input validation and CRC handling
static sc_status_t validate_encrypt_inputs(sc_context_t *ctx, const uint8_t *plaintext,
const uint8_t *ciphertext, const size_t *ciphertext_len,
size_t plaintext_len) {
if (!ctx || !plaintext || !ciphertext || !ciphertext_len) return SC_ERR_INVALID_ARG;
if (!ctx->session_ready) return SC_ERR_NOT_INITIALIZED;
if (plaintext_len == 0) return SC_ERR_INVALID_ARG;
return SC_OK;
}
static sc_status_t validate_decrypt_inputs(sc_context_t *ctx, const uint8_t *ciphertext,
const uint8_t *plaintext, const size_t *plaintext_len,
size_t ciphertext_len) {
if (!ctx || !ciphertext || !plaintext || !plaintext_len) return SC_ERR_INVALID_ARG;
if (!ctx->session_ready) return SC_ERR_NOT_INITIALIZED;
if (ciphertext_len < SC_NONCE_SIZE + SC_TAG_SIZE + SC_CRC32_SIZE) return SC_ERR_INVALID_ARG;
return SC_OK;
}
static void append_crc32(const uint8_t *plaintext, size_t plaintext_len, uint8_t *output) {
memcpy(output, plaintext, plaintext_len);
uint32_t crc = crc32_calc(plaintext, plaintext_len);
output[plaintext_len] = (crc >> 0) & 0xFF;
output[plaintext_len + 1] = (crc >> 8) & 0xFF;
output[plaintext_len + 2] = (crc >> 16) & 0xFF;
output[plaintext_len + 3] = (crc >> 24) & 0xFF;
}
static sc_status_t verify_and_strip_crc32(uint8_t *plaintext_with_crc, size_t total_len,
uint8_t *plaintext, size_t *plaintext_len) {
size_t data_len = total_len - SC_CRC32_SIZE;
uint32_t received_crc = ((uint32_t)plaintext_with_crc[data_len]) |
((uint32_t)plaintext_with_crc[data_len + 1] << 8) |
((uint32_t)plaintext_with_crc[data_len + 2] << 16) |
((uint32_t)plaintext_with_crc[data_len + 3] << 24);
uint32_t calc_crc = crc32_calc(plaintext_with_crc, data_len);
if (received_crc != calc_crc) return SC_ERR_CRC_FAILED;
memcpy(plaintext, plaintext_with_crc, data_len);
*plaintext_len = data_len;
return SC_OK;
}
static void sc_derive_session_key(const uint8_t *shared_secret, uint8_t *session_key) {
SC_SHA256_CTX sha_ctx;
uint8_t hash[SC_HASH_SIZE];
sc_sha256_init(&sha_ctx);
sc_sha256_update(&sha_ctx, shared_secret, SC_SHARED_SECRET_SIZE);
sc_sha256_update(&sha_ctx, (const uint8_t *)"uTun-v3-session", 15);
sc_sha256_final(&sha_ctx, hash);
memcpy(session_key, hash, SC_SESSION_KEY_SIZE);
}
// Derive 12-byte nonce for stream cipher from session key + stream_id
static void sc_stream_derive_nonce(const uint8_t *session_key, uint32_t stream_id, uint8_t *nonce_out) {
SC_SHA256_CTX sha_ctx;
uint8_t hash[SC_HASH_SIZE];
uint8_t stream_id_le[4];
stream_id_le[0] = (uint8_t)(stream_id >> 0);
stream_id_le[1] = (uint8_t)(stream_id >> 8);
stream_id_le[2] = (uint8_t)(stream_id >> 16);
stream_id_le[3] = (uint8_t)(stream_id >> 24);
sc_sha256_init(&sha_ctx);
sc_sha256_update(&sha_ctx, session_key, SC_SESSION_KEY_SIZE);
sc_sha256_update(&sha_ctx, stream_id_le, sizeof(stream_id_le));
sc_sha256_update(&sha_ctx, (const uint8_t *)"uTun3-stream", 12);
sc_sha256_final(&sha_ctx, hash);
memcpy(nonce_out, hash, SC_STREAM_NONCE_SIZE);
}
// X25519: any non-zero 32-byte value is a valid public key
static int sc_validate_key(const uint8_t *public_key) {
uint8_t zero[SC_PUBKEY_SIZE] = {0};
if (memcmp(public_key, zero, SC_PUBKEY_SIZE) == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "all-zero public key");
return -1;
}
return 0;
}
sc_status_t sc_generate_keypair(struct SC_MYKEYS *pk) {
if (!pk) return SC_ERR_INVALID_ARG;
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL);
if (!ctx) return SC_ERR_CRYPTO;
if (EVP_PKEY_keygen_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); return SC_ERR_CRYPTO; }
EVP_PKEY *pkey = NULL;
if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { EVP_PKEY_CTX_free(ctx); return SC_ERR_CRYPTO; }
EVP_PKEY_CTX_free(ctx);
size_t priv_len = SC_PRIVKEY_SIZE, pub_len = SC_PUBKEY_SIZE;
if (EVP_PKEY_get_raw_private_key(pkey, pk->private_key, &priv_len) <= 0
|| priv_len != SC_PRIVKEY_SIZE
|| EVP_PKEY_get_raw_public_key(pkey, pk->public_key, &pub_len) <= 0
|| pub_len != SC_PUBKEY_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "failed to extract raw keys");
EVP_PKEY_free(pkey);
return SC_ERR_CRYPTO;
}
EVP_PKEY_free(pkey);
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "generated valid X25519 keypair");
return SC_OK;
}
sc_status_t sc_init_local_keys(struct SC_MYKEYS *mykeys, const char *public_key, const char *private_key) {
if (!mykeys || !public_key || !private_key) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid arguments");
return SC_ERR_INVALID_ARG;
}
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "public_key len=%zu, private_key len=%zu",
strlen(public_key), strlen(private_key));
if (hex_to_binary(public_key, mykeys->public_key, SC_PUBKEY_SIZE)) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "failed to convert public key from hex");
return SC_ERR_INVALID_ARG;
}
if (hex_to_binary(private_key, mykeys->private_key, SC_PRIVKEY_SIZE)) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "failed to convert private key from hex");
return SC_ERR_INVALID_ARG;
}
if (sc_validate_key(mykeys->public_key) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "public key validation failed");
return SC_ERR_INVALID_ARG;
}
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "keys initialized successfully");
return SC_OK;
}
sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const uint8_t *peer_public_key_h, int mode) {
uint8_t peer_public_key[SC_PUBKEY_SIZE];
if (mode) {
if (hex_to_binary((const char*)peer_public_key_h, peer_public_key, SC_PUBKEY_SIZE)) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid hex key format");
return SC_ERR_INVALID_ARG;
}
} else {
memcpy(peer_public_key, peer_public_key_h, SC_PUBKEY_SIZE);
}
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid ctx"); return SC_ERR_INVALID_ARG; }
if (!ctx->initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "ctx not initialized"); return SC_ERR_NOT_INITIALIZED; }
if (sc_validate_key(peer_public_key) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid key"); return SC_ERR_INVALID_ARG; }
if (!ctx->pk) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "no private key"); return SC_ERR_NOT_INITIALIZED; }
EVP_PKEY *my_pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, ctx->pk->private_key, SC_PRIVKEY_SIZE);
if (!my_pkey) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_new_raw_private_key failed"); return SC_ERR_CRYPTO; }
EVP_PKEY *peer_pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, peer_public_key, SC_PUBKEY_SIZE);
if (!peer_pkey) { EVP_PKEY_free(my_pkey); DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_new_raw_public_key failed"); return SC_ERR_CRYPTO; }
EVP_PKEY_CTX *derive_ctx = EVP_PKEY_CTX_new(my_pkey, NULL);
if (!derive_ctx) { EVP_PKEY_free(my_pkey); EVP_PKEY_free(peer_pkey); return SC_ERR_CRYPTO; }
if (EVP_PKEY_derive_init(derive_ctx) <= 0) {
EVP_PKEY_CTX_free(derive_ctx); EVP_PKEY_free(my_pkey); EVP_PKEY_free(peer_pkey);
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_derive_init failed");
return SC_ERR_CRYPTO;
}
if (EVP_PKEY_derive_set_peer(derive_ctx, peer_pkey) <= 0) {
EVP_PKEY_CTX_free(derive_ctx); EVP_PKEY_free(my_pkey); EVP_PKEY_free(peer_pkey);
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_derive_set_peer failed");
return SC_ERR_CRYPTO;
}
uint8_t shared_secret[SC_SHARED_SECRET_SIZE];
size_t secret_len = SC_SHARED_SECRET_SIZE;
if (EVP_PKEY_derive(derive_ctx, shared_secret, &secret_len) <= 0 || secret_len != SC_SHARED_SECRET_SIZE) {
EVP_PKEY_CTX_free(derive_ctx); EVP_PKEY_free(my_pkey); EVP_PKEY_free(peer_pkey);
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "X25519 derive failed secret_len=%zu", secret_len);
return SC_ERR_CRYPTO;
}
sc_derive_session_key(shared_secret, ctx->session_key);
memcpy(ctx->peer_public_key, peer_public_key, SC_PUBKEY_SIZE);
ctx->peer_key_set = 1;
ctx->session_ready = 1;
EVP_PKEY_CTX_free(derive_ctx);
EVP_PKEY_free(my_pkey);
EVP_PKEY_free(peer_pkey);
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "X25519 key exchange complete");
return SC_OK;
}
static void sc_build_nonce(uint64_t counter, uint8_t *nonce_out) {
SHA256_CTX sha_ctx;
uint8_t hash[32];
struct timeval tv;
uint8_t data[24];
if (!sc_urandom_initialized) sc_init_random_seed();
utun_gettimeofday(&tv, NULL);
memcpy(data, sc_urandom_seed, 8);
data[8] = (counter >> 0) & 0xFF; data[9] = (counter >> 8) & 0xFF;
data[10] = (counter >> 16) & 0xFF; data[11] = (counter >> 24) & 0xFF;
data[12] = (counter >> 32) & 0xFF; data[13] = (counter >> 40) & 0xFF;
data[14] = (counter >> 48) & 0xFF; data[15] = (counter >> 56) & 0xFF;
data[16] = (tv.tv_sec >> 0) & 0xFF; data[17] = (tv.tv_sec >> 8) & 0xFF;
data[18] = (tv.tv_sec >> 16) & 0xFF; data[19] = (tv.tv_sec >> 24) & 0xFF;
data[20] = (tv.tv_usec >> 0) & 0xFF; data[21] = (tv.tv_usec >> 8) & 0xFF;
data[22] = (tv.tv_usec >> 16) & 0xFF; data[23] = (tv.tv_usec >> 24) & 0xFF;
SHA256_Init(&sha_ctx);
SHA256_Update(&sha_ctx, data, 24);
SHA256_Final(hash, &sha_ctx);
memcpy(nonce_out, hash, SC_NONCE_SIZE);
}
sc_status_t sc_encrypt(sc_context_t *ctx, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t *ciphertext_len) {
sc_status_t status = validate_encrypt_inputs(ctx, plaintext, ciphertext, ciphertext_len, plaintext_len);
if (status != SC_OK) return status;
uint8_t plaintext_with_crc[plaintext_len + SC_CRC32_SIZE];
append_crc32(plaintext, plaintext_len, plaintext_with_crc);
size_t total_plaintext_len = plaintext_len + SC_CRC32_SIZE;
uint8_t nonce[SC_NONCE_SIZE];
sc_build_nonce(ctx->tx_counter, nonce);
EVP_CIPHER_CTX *ectx = EVP_CIPHER_CTX_new();
if (!ectx) return SC_ERR_CRYPTO;
if (EVP_EncryptInit_ex(ectx, EVP_aes_128_ccm(), NULL, NULL, NULL) != 1
|| EVP_CIPHER_CTX_ctrl(ectx, EVP_CTRL_AEAD_SET_IVLEN, SC_NONCE_SIZE, NULL) != 1
|| EVP_CIPHER_CTX_ctrl(ectx, EVP_CTRL_AEAD_SET_TAG, SC_TAG_SIZE, NULL) != 1
|| EVP_EncryptInit_ex(ectx, NULL, NULL, ctx->session_key, nonce) != 1) {
EVP_CIPHER_CTX_free(ectx); return SC_ERR_CRYPTO;
}
int outlen;
uint8_t outbuf[total_plaintext_len];
if (EVP_EncryptUpdate(ectx, outbuf, &outlen, plaintext_with_crc, total_plaintext_len) != 1
|| outlen != (int)total_plaintext_len) {
EVP_CIPHER_CTX_free(ectx); return SC_ERR_CRYPTO;
}
int tmp;
if (EVP_EncryptFinal_ex(ectx, outbuf + outlen, &tmp) != 1) { EVP_CIPHER_CTX_free(ectx); return SC_ERR_CRYPTO; }
uint8_t tag[SC_TAG_SIZE];
if (EVP_CIPHER_CTX_ctrl(ectx, EVP_CTRL_AEAD_GET_TAG, SC_TAG_SIZE, tag) != 1) { EVP_CIPHER_CTX_free(ectx); return SC_ERR_CRYPTO; }
memcpy(ciphertext, nonce, SC_NONCE_SIZE);
memcpy(ciphertext + SC_NONCE_SIZE, outbuf, total_plaintext_len);
memcpy(ciphertext + SC_NONCE_SIZE + total_plaintext_len, tag, SC_TAG_SIZE);
*ciphertext_len = SC_NONCE_SIZE + total_plaintext_len + SC_TAG_SIZE;
ctx->tx_counter++;
EVP_CIPHER_CTX_free(ectx);
return SC_OK;
}
sc_status_t sc_decrypt(sc_context_t *ctx, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t *plaintext_len) {
sc_status_t status = validate_decrypt_inputs(ctx, ciphertext, plaintext, plaintext_len, ciphertext_len);
if (status != SC_OK) return status;
uint8_t nonce[SC_NONCE_SIZE];
memcpy(nonce, ciphertext, SC_NONCE_SIZE);
const uint8_t *encrypted_data = ciphertext + SC_NONCE_SIZE;
size_t encrypted_len = ciphertext_len - SC_NONCE_SIZE;
size_t total_plaintext_len = encrypted_len - SC_TAG_SIZE;
uint8_t plaintext_with_crc[total_plaintext_len];
EVP_CIPHER_CTX *dctx = EVP_CIPHER_CTX_new();
if (!dctx) return SC_ERR_CRYPTO;
if (EVP_DecryptInit_ex(dctx, EVP_aes_128_ccm(), NULL, NULL, NULL) != 1
|| EVP_CIPHER_CTX_ctrl(dctx, EVP_CTRL_AEAD_SET_IVLEN, SC_NONCE_SIZE, NULL) != 1
|| EVP_CIPHER_CTX_ctrl(dctx, EVP_CTRL_AEAD_SET_TAG, SC_TAG_SIZE, (void *)(encrypted_data + total_plaintext_len)) != 1
|| EVP_DecryptInit_ex(dctx, NULL, NULL, ctx->session_key, nonce) != 1) {
EVP_CIPHER_CTX_free(dctx); return SC_ERR_CRYPTO;
}
int outlen;
if (EVP_DecryptUpdate(dctx, plaintext_with_crc, &outlen, encrypted_data, total_plaintext_len) != 1
|| outlen != (int)total_plaintext_len) {
EVP_CIPHER_CTX_free(dctx); return SC_ERR_AUTH_FAILED;
}
int tmp;
if (EVP_DecryptFinal_ex(dctx, plaintext_with_crc + outlen, &tmp) != 1) { EVP_CIPHER_CTX_free(dctx); return SC_ERR_AUTH_FAILED; }
EVP_CIPHER_CTX_free(dctx);
sc_status_t result = verify_and_strip_crc32(plaintext_with_crc, total_plaintext_len, plaintext, plaintext_len);
if (result == SC_OK) ctx->rx_counter++;
return result;
}
sc_status_t sc_compute_public_key_from_private(const uint8_t *private_key, uint8_t *public_key) {
if (!private_key || !public_key) return SC_ERR_INVALID_ARG;
EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, private_key, SC_PRIVKEY_SIZE);
if (!pkey) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_new_raw_private_key failed"); return SC_ERR_CRYPTO; }
size_t pub_len = SC_PUBKEY_SIZE;
if (EVP_PKEY_get_raw_public_key(pkey, public_key, &pub_len) <= 0 || pub_len != SC_PUBKEY_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_get_raw_public_key failed");
EVP_PKEY_free(pkey); return SC_ERR_CRYPTO;
}
EVP_PKEY_free(pkey);
return SC_OK;
}
// --- Streaming cipher (AES-128-CTR) ---
sc_status_t sc_stream_init(sc_context_t *ctx, struct sc_stream_state *state, uint32_t stream_id) {
if (!ctx || !state) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; }
if (!ctx->session_ready) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "session not ready"); return SC_ERR_NOT_INITIALIZED; }
uint8_t nonce[SC_STREAM_NONCE_SIZE];
sc_stream_derive_nonce(ctx->session_key, stream_id, nonce);
uint8_t iv[16];
memcpy(iv, nonce, SC_STREAM_NONCE_SIZE);
memset(iv + SC_STREAM_NONCE_SIZE, 0, 4);
EVP_CIPHER_CTX *ectx = EVP_CIPHER_CTX_new();
if (!ectx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_CIPHER_CTX_new failed"); return SC_ERR_CRYPTO; }
if (EVP_EncryptInit_ex(ectx, EVP_aes_128_ctr(), NULL, ctx->session_key, iv) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_EncryptInit_ex failed");
EVP_CIPHER_CTX_free(ectx); return SC_ERR_CRYPTO;
}
state->ectx = ectx;
state->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "stream_id=%u nonce=%02x%02x%02x%02x...",
stream_id, nonce[0], nonce[1], nonce[2], nonce[3]);
return SC_OK;
}
sc_status_t sc_stream_xor(struct sc_stream_state *state, uint8_t *data, size_t data_len) {
if (data_len == 0) return SC_OK;
if (!state || !data) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; }
if (!state->initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "not initialized"); return SC_ERR_NOT_INITIALIZED; }
int outlen;
if (EVP_EncryptUpdate((EVP_CIPHER_CTX *)state->ectx, data, &outlen, data, (int)data_len) != 1
|| (size_t)outlen != data_len) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_EncryptUpdate failed outlen=%d datalen=%zu", outlen, data_len);
return SC_ERR_CRYPTO;
}
return SC_OK;
}
void sc_stream_cleanup(struct sc_stream_state *state) {
if (!state || !state->ectx) return;
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *)state->ectx);
state->ectx = NULL;
state->initialized = 0;
}
// --- Streaming Ed25519 signature ---
// SHA-512 (инкрементально, EVP_DigestUpdate) → 64-байтный хеш → Ed25519 (one-shot EVP_DigestSign).
// Ed25519 внутренне SHA-512'ит переданное, получается SHA-512(SHA-512(данные)) — консистентно с обеих сторон.
// Память O(1), подходит для потоков произвольного размера.
static int sc_derive_ed25519_key(const uint8_t *x25519_privkey, EVP_PKEY **pkey_out) {
uint8_t hash[SHA512_DIGEST_LENGTH];
SHA512(x25519_privkey, SC_PRIVKEY_SIZE, hash);
*pkey_out = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, hash, SC_PRIVKEY_SIZE);
if (!*pkey_out) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_new_raw_private_key failed"); return SC_ERR_CRYPTO; }
return SC_OK;
}
sc_status_t sc_derive_ed25519_pubkey(const uint8_t *x25519_privkey, uint8_t *ed25519_pubkey_out) {
if (!x25519_privkey || !ed25519_pubkey_out) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; }
EVP_PKEY *pkey = NULL;
if (sc_derive_ed25519_key(x25519_privkey, &pkey) != SC_OK) return SC_ERR_CRYPTO;
size_t pub_len = SC_PUBKEY_SIZE;
if (EVP_PKEY_get_raw_public_key(pkey, ed25519_pubkey_out, &pub_len) <= 0 || pub_len != SC_PUBKEY_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_get_raw_public_key failed");
EVP_PKEY_free(pkey); return SC_ERR_CRYPTO;
}
EVP_PKEY_free(pkey);
return SC_OK;
}
sc_status_t sc_stream_sign_init(sc_context_t *ctx, struct sc_stream_sign_state *state) {
if (!ctx || !state) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; }
if (!ctx->initialized || !ctx->pk) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "ctx not initialized"); return SC_ERR_NOT_INITIALIZED; }
memset(state, 0, sizeof(*state));
if (sc_derive_ed25519_key(ctx->pk->private_key, (EVP_PKEY**)&state->pkey) != SC_OK) return SC_ERR_CRYPTO;
EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
if (!md_ctx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_MD_CTX_new failed"); EVP_PKEY_free((EVP_PKEY*)state->pkey); return SC_ERR_CRYPTO; }
if (EVP_DigestInit_ex(md_ctx, EVP_sha512(), NULL) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_DigestInit_ex failed");
EVP_MD_CTX_free(md_ctx); EVP_PKEY_free((EVP_PKEY*)state->pkey); return SC_ERR_CRYPTO;
}
state->md_ctx = md_ctx;
state->is_sign = 1;
state->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "ok");
return SC_OK;
}
sc_status_t sc_stream_sign_verify_init(struct sc_stream_sign_state *state, const uint8_t *ed25519_pubkey) {
if (!state || !ed25519_pubkey) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; }
memset(state, 0, sizeof(*state));
state->pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, ed25519_pubkey, SC_PUBKEY_SIZE);
if (!state->pkey) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_new_raw_public_key failed"); return SC_ERR_CRYPTO; }
EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
if (!md_ctx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_MD_CTX_new failed"); EVP_PKEY_free((EVP_PKEY*)state->pkey); return SC_ERR_CRYPTO; }
if (EVP_DigestInit_ex(md_ctx, EVP_sha512(), NULL) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_DigestInit_ex failed");
EVP_MD_CTX_free(md_ctx); EVP_PKEY_free((EVP_PKEY*)state->pkey); return SC_ERR_CRYPTO;
}
state->md_ctx = md_ctx;
state->is_sign = 0;
state->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "ok");
return SC_OK;
}
sc_status_t sc_stream_sign_update(struct sc_stream_sign_state *state, const uint8_t *data, size_t len) {
if (len == 0) return SC_OK;
if (!state || !data) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; }
if (!state->initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "not initialized"); return SC_ERR_NOT_INITIALIZED; }
if (EVP_DigestUpdate((EVP_MD_CTX *)state->md_ctx, data, len) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_DigestUpdate failed");
return SC_ERR_CRYPTO;
}
return SC_OK;
}
sc_status_t sc_stream_sign_final(struct sc_stream_sign_state *state, uint8_t *sig_out, size_t *sig_len) {
if (!state || !sig_out || !sig_len) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; }
if (!state->initialized || !state->is_sign) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "not initialized for sign"); return SC_ERR_NOT_INITIALIZED; }
uint8_t hash[EVP_MAX_MD_SIZE]; unsigned hash_len = sizeof(hash);
if (EVP_DigestFinal_ex((EVP_MD_CTX *)state->md_ctx, hash, &hash_len) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_DigestFinal_ex failed");
sc_stream_sign_cleanup(state); return SC_ERR_CRYPTO;
}
EVP_MD_CTX *sctx = EVP_MD_CTX_new();
if (!sctx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_MD_CTX_new failed"); sc_stream_sign_cleanup(state); return SC_ERR_CRYPTO; }
if (EVP_DigestSignInit(sctx, NULL, NULL, NULL, (EVP_PKEY*)state->pkey) != 1
|| EVP_DigestSign(sctx, sig_out, sig_len, hash, hash_len) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_DigestSign failed");
EVP_MD_CTX_free(sctx); sc_stream_sign_cleanup(state); return SC_ERR_CRYPTO;
}
EVP_MD_CTX_free(sctx);
sc_stream_sign_cleanup(state);
return SC_OK;
}
sc_status_t sc_stream_sign_verify(struct sc_stream_sign_state *state, const uint8_t *sig, size_t sig_len) {
if (!state || !sig) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; }
if (!state->initialized || state->is_sign) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "not initialized for verify"); return SC_ERR_NOT_INITIALIZED; }
uint8_t hash[EVP_MAX_MD_SIZE]; unsigned hash_len = sizeof(hash);
if (EVP_DigestFinal_ex((EVP_MD_CTX *)state->md_ctx, hash, &hash_len) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_DigestFinal_ex failed");
sc_stream_sign_cleanup(state); return SC_ERR_CRYPTO;
}
EVP_MD_CTX *vctx = EVP_MD_CTX_new();
if (!vctx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_MD_CTX_new failed"); sc_stream_sign_cleanup(state); return SC_ERR_CRYPTO; }
if (EVP_DigestVerifyInit(vctx, NULL, NULL, NULL, (EVP_PKEY*)state->pkey) != 1
|| EVP_DigestVerify(vctx, sig, sig_len, hash, hash_len) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_DigestVerify failed");
EVP_MD_CTX_free(vctx); sc_stream_sign_cleanup(state); return SC_ERR_AUTH_FAILED;
}
EVP_MD_CTX_free(vctx);
sc_stream_sign_cleanup(state);
return SC_OK;
}
void sc_stream_sign_cleanup(struct sc_stream_sign_state *state) {
if (!state) return;
if (state->md_ctx) { EVP_MD_CTX_free((EVP_MD_CTX *)state->md_ctx); state->md_ctx = NULL; }
if (state->pkey) { EVP_PKEY_free((EVP_PKEY *)state->pkey); state->pkey = NULL; }
state->initialized = 0;
}
// --- Common crypto utilities ---
sc_status_t sc_sha_transcode(const uint8_t *key, size_t key_len, uint8_t *data, size_t data_len) {
if (!key || !data || key_len == 0 || data_len == 0) return SC_ERR_INVALID_ARG;
uint8_t sha_hash[SC_HASH_SIZE];
SC_SHA256_CTX ctx;
sc_sha256_init(&ctx);
sc_sha256_update(&ctx, key, key_len);
sc_sha256_final(&ctx, sha_hash);
for (size_t i = 0; i < data_len; i++) data[i] ^= sha_hash[i % SC_HASH_SIZE];
return SC_OK;
}
sc_status_t sc_obfuscate_pubkey(const uint8_t *salt, const uint8_t *peer_pubkey, const uint8_t *pubkey, uint8_t *output) {
if (!salt || !peer_pubkey || !pubkey || !output) return SC_ERR_INVALID_ARG;
uint8_t sha[SC_HASH_SIZE*2];
SC_SHA256_CTX ctx;
sc_sha256_init(&ctx);
sc_sha256_update(&ctx, salt, SC_PUBKEY_ENC_SALT_SIZE);
sc_sha256_update(&ctx, peer_pubkey, SC_PUBKEY_SIZE);
sc_sha256_final(&ctx, sha);
sc_sha256_init(&ctx);
sc_sha256_update(&ctx, peer_pubkey, SC_PUBKEY_SIZE);
sc_sha256_update(&ctx, salt, SC_PUBKEY_ENC_SALT_SIZE);
sc_sha256_final(&ctx, sha+SC_HASH_SIZE);
for (size_t i = 0; i < SC_PUBKEY_SIZE; i++) output[i] = pubkey[i] ^ sha[i];
return SC_OK;
}