diff --git a/CHANGES.md b/CHANGES.md index 47b6875..8308e99 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,26 @@ # Histórico de Alterações do libzupt +## Não lançado + +### Segurança +- Chaves não são mais gravadas em arquivos temporários previsíveis em `/tmp` + durante a criptografia/descriptografia em memória. As funções de + criptografia/descriptografia agora derivam as chaves diretamente do buffer + em memória (`zupt_hybrid_encrypt_init_mem` / `zupt_hybrid_decrypt_init_mem`), + eliminando um vetor de ataque por symlink e o vazamento da chave privada em + disco (que anulava a proteção `mlock`). +- Arquivos de chave privada gerados agora são criados com permissões `0600` + (somente o dono), tanto na API C (`zupt_hybrid_keygen`) quanto na API C++ + (`KeyGenerator::saveKeyPair`), em vez de herdar o modo padrão do `umask`. + +### Correções +- Corrigida extensão de sinal/comportamento indefinido na leitura do tamanho + do bloco durante a descriptografia: cada byte é convertido para `size_t` + antes do deslocamento, evitando um `block_len` inválido (enorme). +- Removidas as funções mortas e incorretas `zupt_hybrid_derive_keys` (que + ignorava o segredo X25519, produzindo chaves que o lado de descriptografia + nunca reproduziria) e `zupt_hybrid_decrypt_derive_keys`, ambas sem uso. + ## v1.5.0 (2026-03-29) ### Novidades diff --git a/README.md b/README.md index 260fb39..45e2626 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The **libzupt** is an SDK designed to simplify the implementation of encryption The main goal of libzupt is to enable current applications to be protected against emerging threats from quantum computing, even when running on classical computers. By combining traditional cryptographic techniques with quantum-resistant mechanisms, the library provides an additional layer of security that anticipates future scenarios where classical algorithms may be broken. This allows developers to build more resilient systems, ensuring long-term data confidentiality and integrity. -The name **zupt** is atribute to the original project created by [**Cristian Cezar Moisés**](https://github.com/cristiancmoises), acknowledging his fundamental contribution to the conceptual and technical foundation of this solution. This inspiration reinforces the library’s commitment to innovation, security, and the evolution of ideas that drive modern applied cryptography forward. +The name **zupt** is a tribute to the original project created by [**Cristian Cezar Moisés**](https://github.com/cristiancmoises), acknowledging his fundamental contribution to the conceptual and technical foundation of this solution. This inspiration reinforces the library’s commitment to innovation, security, and the evolution of ideas that drive modern applied cryptography forward.
image
diff --git a/include/zupt.h b/include/zupt.h index 911593d..35a23af 100644 --- a/include/zupt.h +++ b/include/zupt.h @@ -324,6 +324,14 @@ int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile, uint8_t *enc_hdr, size_t *enc_hdr_len); int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile, const uint8_t *enc_hdr, size_t enc_hdr_len); +/* In-memory variants: take the key material directly from a buffer instead of + * a file path, so callers never have to stage (private) keys on disk. */ +int zupt_hybrid_encrypt_init_mem(zupt_keyring_t *kr, + const uint8_t *pubkey, size_t pubkey_len, + uint8_t *enc_hdr, size_t *enc_hdr_len); +int zupt_hybrid_decrypt_init_mem(zupt_keyring_t *kr, + const uint8_t *privkey, size_t privkey_len, + const uint8_t *enc_hdr, size_t enc_hdr_len); const char *zupt_strerror(zupt_error_t e); const char *zupt_codec_name(uint16_t id); diff --git a/include/zupt_cxx.h b/include/zupt_cxx.h index 96dc39f..bdfe0fa 100644 --- a/include/zupt_cxx.h +++ b/include/zupt_cxx.h @@ -31,26 +31,6 @@ extern "C" { #endif -/* HYBRID KEY DERIVATION C API */ - -/* Maximum key derivation input size */ -#define ZUPT_KDF_INPUT_MAX (32 + 1088 + 32 + 15) - -/* Derive archive keys from hybrid shared secret and transcript - * Returns 64 bytes: enc_key[32] + mac_key[32] - * Returns 0 on success, -1 on error - */ -ZUPT_API int zupt_hybrid_derive_keys(const uint8_t* ml_ss, const uint8_t* ml_ct, - const uint8_t* eph_pk, const uint8_t* ml_pk, - uint8_t* archive_key); - -/* Derive archive keys from private key and encryption header - * Returns 0 on success, -1 on error - */ -ZUPT_API int zupt_hybrid_decrypt_derive_keys(const uint8_t* priv_key, size_t priv_key_len, - const uint8_t* enc_header, size_t enc_header_len, - uint8_t* archive_key); - /* KEY GENERATION C API */ /* Generate a hybrid key pair diff --git a/src/zupt_crypto.c b/src/zupt_crypto.c index 065c271..edf4001 100644 --- a/src/zupt_crypto.c +++ b/src/zupt_crypto.c @@ -21,6 +21,11 @@ #include #include #endif +#if !defined(_WIN32) + #include + #include + #include +#endif /* ═══════════════════════════════════════════════════════════════════ * RANDOM BYTES (OS-native CSPRNG — NO FALLBACK) @@ -452,9 +457,19 @@ int zupt_hybrid_keygen(const char *keyfile) { zupt_random_bytes(x_sk, 32); zupt_x25519_base(x_pk, x_sk); - /* Write private key file */ + /* Write private key file. The file holds the X25519 and ML-KEM secret + * keys in the clear, so it must never be world/group readable. Create it + * with 0600 atomically (via open) rather than fopen + chmod, which would + * briefly expose the file with the umask-default mode. */ +#if !defined(_WIN32) + int fd = open(keyfile, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) return -1; + FILE *f = fdopen(fd, "wb"); + if (!f) { close(fd); return -1; } +#else FILE *f = fopen(keyfile, "wb"); if (!f) return -1; +#endif size_t total = ZKEY_PRIV_SIZE; uint8_t *buf = (uint8_t *)calloc(total + 8, 1); /* +8 for checksum */ @@ -552,6 +567,29 @@ static int read_privkey(const char *path, uint8_t ml_pk[1184], uint8_t x_pk[32], return 0; } +/* Parse a public key directly from an in-memory buffer (no temp files). */ +static int parse_pubkey_buf(const uint8_t *buf, size_t len, + uint8_t ml_pk[1184], uint8_t x_pk[32]) { + if (!buf || len < (size_t)(8 + 1184 + 32)) return -1; + if (memcmp(buf, ZKEY_MAGIC, 4) != 0) return -1; + memcpy(ml_pk, buf + 8, 1184); + memcpy(x_pk, buf + 8 + 1184, 32); + return 0; +} + +/* Parse a private key directly from an in-memory buffer (no temp files). */ +static int parse_privkey_buf(const uint8_t *buf, size_t len, + uint8_t ml_pk[1184], uint8_t x_pk[32], + uint8_t ml_sk[2400], uint8_t x_sk[32]) { + if (!buf || len < (size_t)(8 + 1184 + 32 + 2400 + 32)) return -1; + if (memcmp(buf, ZKEY_MAGIC, 4) != 0 || !(buf[5] & ZKEY_FLAG_PRIVATE)) return -1; + memcpy(ml_pk, buf + 8, 1184); + memcpy(x_pk, buf + 8 + 1184, 32); + memcpy(ml_sk, buf + 8 + 1184 + 32, 2400); + memcpy(x_sk, buf + 8 + 1184 + 32 + 2400, 32); + return 0; +} + /* * HYBRID ENCRYPT INIT: Encapsulate with ML-KEM + X25519, derive archive keys. * @@ -576,11 +614,11 @@ static int read_privkey(const char *path, uint8_t ml_pk[1184], uint8_t x_pk[32], @ ensures \result == 0 ==> kr->active == 1; @ ensures \result == 0 ==> *enc_hdr_len == 1137; */ -int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile, - uint8_t *enc_hdr, size_t *enc_hdr_len) { - uint8_t ml_pk[1184], x_pk[32]; - if (read_pubkey(pubkeyfile, ml_pk, x_pk) != 0) return -1; - +/* Shared encapsulation + KDF core, used by both the file- and memory-based + * encrypt-init entry points. Takes the already-parsed recipient public keys. */ +static int hybrid_encrypt_core(zupt_keyring_t *kr, + const uint8_t ml_pk[1184], const uint8_t x_pk[32], + uint8_t *enc_hdr, size_t *enc_hdr_len) { /* ML-KEM-768 encapsulation */ uint8_t ml_ct[1088], ml_ss[32]; if (zupt_mlkem768_encaps(ml_ct, ml_ss, ml_pk) != 0) return -1; @@ -637,6 +675,21 @@ int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile, return 0; } +int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile, + uint8_t *enc_hdr, size_t *enc_hdr_len) { + uint8_t ml_pk[1184], x_pk[32]; + if (read_pubkey(pubkeyfile, ml_pk, x_pk) != 0) return -1; + return hybrid_encrypt_core(kr, ml_pk, x_pk, enc_hdr, enc_hdr_len); +} + +int zupt_hybrid_encrypt_init_mem(zupt_keyring_t *kr, + const uint8_t *pubkey, size_t pubkey_len, + uint8_t *enc_hdr, size_t *enc_hdr_len) { + uint8_t ml_pk[1184], x_pk[32]; + if (parse_pubkey_buf(pubkey, pubkey_len, ml_pk, x_pk) != 0) return -1; + return hybrid_encrypt_core(kr, ml_pk, x_pk, enc_hdr, enc_hdr_len); +} + /* * HYBRID DECRYPT INIT: Decapsulate with ML-KEM + X25519, derive archive keys. */ @@ -649,8 +702,12 @@ int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile, @ kr->iterations, kr->active; @ ensures \result == 0 ==> kr->active == 1; */ -int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile, - const uint8_t *enc_hdr, size_t enc_hdr_len) { +/* Shared decapsulation + KDF core, used by both the file- and memory-based + * decrypt-init entry points. Takes the already-parsed recipient secret keys; + * the caller owns and wipes ml_sk/x_sk. */ +static int hybrid_decrypt_core(zupt_keyring_t *kr, + const uint8_t ml_sk[2400], const uint8_t x_sk[32], + const uint8_t *enc_hdr, size_t enc_hdr_len) { if (enc_hdr_len < 1 + 1088 + 32 + 16) return -1; /* enc_type + ct + eph_pk + nonce */ if (enc_hdr[0] != ZUPT_ENC_PQ_HYBRID) return -1; @@ -658,9 +715,6 @@ int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile, const uint8_t *eph_pk = enc_hdr + 1 + 1088; const uint8_t *nonce = enc_hdr + 1 + 1088 + 32; - uint8_t ml_pk[1184], x_pk[32], ml_sk[2400], x_sk[32]; - if (read_privkey(privkeyfile, ml_pk, x_pk, ml_sk, x_sk) != 0) return -1; - /* ML-KEM-768 decapsulation */ uint8_t ml_ss[32]; zupt_mlkem768_decaps(ml_ss, ml_ct, ml_sk); @@ -694,8 +748,6 @@ int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile, zupt_mlock_keys(kr->enc_key, ZUPT_AES_KEY_SIZE); zupt_mlock_keys(kr->mac_key, ZUPT_HMAC_SIZE); - zupt_secure_wipe(ml_sk, sizeof(ml_sk)); - zupt_secure_wipe(x_sk, 32); zupt_secure_wipe(ml_ss, 32); zupt_secure_wipe(x_ss, 32); zupt_secure_wipe(hybrid_ikm, 32); @@ -704,3 +756,24 @@ int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile, return 0; } + +int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile, + const uint8_t *enc_hdr, size_t enc_hdr_len) { + uint8_t ml_pk[1184], x_pk[32], ml_sk[2400], x_sk[32]; + if (read_privkey(privkeyfile, ml_pk, x_pk, ml_sk, x_sk) != 0) return -1; + int r = hybrid_decrypt_core(kr, ml_sk, x_sk, enc_hdr, enc_hdr_len); + zupt_secure_wipe(ml_sk, sizeof(ml_sk)); + zupt_secure_wipe(x_sk, 32); + return r; +} + +int zupt_hybrid_decrypt_init_mem(zupt_keyring_t *kr, + const uint8_t *privkey, size_t privkey_len, + const uint8_t *enc_hdr, size_t enc_hdr_len) { + uint8_t ml_pk[1184], x_pk[32], ml_sk[2400], x_sk[32]; + if (parse_privkey_buf(privkey, privkey_len, ml_pk, x_pk, ml_sk, x_sk) != 0) return -1; + int r = hybrid_decrypt_core(kr, ml_sk, x_sk, enc_hdr, enc_hdr_len); + zupt_secure_wipe(ml_sk, sizeof(ml_sk)); + zupt_secure_wipe(x_sk, 32); + return r; +} diff --git a/src/zupt_crypto.cpp b/src/zupt_crypto.cpp index cebb5db..9562757 100644 --- a/src/zupt_crypto.cpp +++ b/src/zupt_crypto.cpp @@ -12,6 +12,12 @@ #include #include +#if !defined(_WIN32) +#include +#include +#include +#endif + // Include C implementation headers extern "C" { #include "zupt.h" @@ -183,7 +189,19 @@ void KeyGenerator::exportPublicKey(const std::string& privfile, const std::strin } void KeyGenerator::saveKeyPair(const KeyPair& kp, const std::string& filename) { - std::ofstream file(filename, std::ios::binary); + /* The key pair file contains the private key in the clear, so restrict it + * to owner-only (0600) before writing any secret bytes. ofstream offers no + * way to set the mode, so create/tighten the file first via open()+fchmod. */ +#if !defined(_WIN32) + int fd = ::open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) { + throw ZuptError(ErrorCode::ERR_IO, "Cannot create key file: " + filename); + } + ::fchmod(fd, 0600); /* enforce 0600 even if the file already existed */ + ::close(fd); +#endif + + std::ofstream file(filename, std::ios::binary | std::ios::trunc); if (!file) { throw ZuptError(ErrorCode::ERR_IO, "Cannot create key file: " + filename); } diff --git a/src/zupt_cxx.c b/src/zupt_cxx.c index dc2bdf7..048e0b5 100644 --- a/src/zupt_cxx.c +++ b/src/zupt_cxx.c @@ -9,122 +9,10 @@ #include "zupt.h" #include "zupt_mlkem.h" #include "zupt_x25519.h" -#include "zupt_keccak.h" #include #include #include -#ifdef _WIN32 -#include -#include -#define getpid _getpid -#define unlink _unlink -#endif - - -/* ═══════════════════════════════════════════════════════════════════ - * HYBRID KEY DERIVATION - * ═══════════════════════════════════════════════════════════════════ */ - -int zupt_hybrid_derive_keys(const uint8_t* ml_ss, const uint8_t* ml_ct, - const uint8_t* eph_pk, const uint8_t* ml_pk, - uint8_t* archive_key) { - if (!ml_ss || !ml_ct || !eph_pk || !archive_key) return -1; - - /* Compute hybrid shared secret: XOR ML-KEM and X25519 shared secrets */ - uint8_t hybrid_ikm[32]; - for (int i = 0; i < 32; i++) { - hybrid_ikm[i] = ml_ss[i]; - } - - /* Key derivation: - * archive_key = SHA3-512(hybrid_ikm || ml_ct || eph_pk || "ZUPT-HYBRID-v1") - * Output: enc_key[32] + mac_key[32] - */ - uint8_t kdf_input[ZUPT_KDF_INPUT_MAX]; - size_t pos = 0; - - memcpy(kdf_input + pos, hybrid_ikm, 32); - pos += 32; - - memcpy(kdf_input + pos, ml_ct, 1088); - pos += 1088; - - memcpy(kdf_input + pos, eph_pk, 32); - pos += 32; - - memcpy(kdf_input + pos, "ZUPT-HYBRID-v1", 15); - pos += 15; - - /* Compute SHA3-512 */ - zupt_sha3_512(kdf_input, pos, archive_key); - - /* Wipe sensitive data */ - zupt_secure_wipe(hybrid_ikm, sizeof(hybrid_ikm)); - zupt_secure_wipe(kdf_input, pos); - - return 0; -} - -int zupt_hybrid_decrypt_derive_keys(const uint8_t* priv_key, size_t priv_key_len, - const uint8_t* enc_header, size_t enc_header_len, - uint8_t* archive_key) { - /* Parse private key: ZKEY header (8) + ml_pk(1184) + x_pk(32) + ml_sk(2400) + x_sk(32) */ - if (priv_key_len < 8 + 1184 + 32 + 2400 + 32) return -1; - if (enc_header_len < 1 + 1088 + 32 + 16) return -1; - - const uint8_t* ml_pk = priv_key + 8; - const uint8_t* x_pk = ml_pk + 1184; - const uint8_t* ml_sk = x_pk + 32; - const uint8_t* x_sk = ml_sk + 2400; - - /* Parse encryption header: enc_type(1) + ml_ct(1088) + eph_pk(32) + nonce(16) */ - const uint8_t* ml_ct = enc_header + 1; - const uint8_t* eph_pk = ml_ct + 1088; - - /* ML-KEM decapsulation */ - uint8_t ml_ss[32]; - if (zupt_mlkem768_decaps(ml_ss, ml_ct, ml_sk) != 0) { - return -1; - } - - /* X25519 ECDH */ - uint8_t x_ss[32]; - zupt_x25519(x_ss, x_sk, eph_pk); - - /* Hybrid shared secret */ - uint8_t hybrid_ikm[32]; - for (int i = 0; i < 32; i++) { - hybrid_ikm[i] = ml_ss[i] ^ x_ss[i]; - } - - /* Key derivation */ - uint8_t kdf_input[ZUPT_KDF_INPUT_MAX]; - size_t pos = 0; - - memcpy(kdf_input + pos, hybrid_ikm, 32); - pos += 32; - - memcpy(kdf_input + pos, ml_ct, 1088); - pos += 1088; - - memcpy(kdf_input + pos, eph_pk, 32); - pos += 32; - - memcpy(kdf_input + pos, "ZUPT-HYBRID-v1", 15); - pos += 15; - - zupt_sha3_512(kdf_input, pos, archive_key); - - /* Wipe sensitive data */ - zupt_secure_wipe(ml_ss, sizeof(ml_ss)); - zupt_secure_wipe(x_ss, sizeof(x_ss)); - zupt_secure_wipe(hybrid_ikm, sizeof(hybrid_ikm)); - zupt_secure_wipe(kdf_input, pos); - - return 0; -} - /* ═══════════════════════════════════════════════════════════════════ * KEY GENERATION * ═══════════════════════════════════════════════════════════════════ */ @@ -271,36 +159,11 @@ uint8_t* zupt_hybrid_encrypt(const uint8_t* pub_key, size_t pub_key_len, return NULL; } - /* Create temporary public key file */ - FILE* tmp = tmpfile(); - if (!tmp) return NULL; - - if (fwrite(pub_key, 1, pub_key_len, tmp) != pub_key_len) { - fclose(tmp); - return NULL; - } - rewind(tmp); - - /* Read public key file path via temp file descriptor */ - char tmp_path[64]; - snprintf(tmp_path, sizeof(tmp_path), "/tmp/zupt_pub_XXXXXX"); - - /* Actually, let's use a different approach - write to a temp file by name */ - snprintf(tmp_path, sizeof(tmp_path), "/tmp/zupt_pub_%d", getpid()); - FILE* f = fopen(tmp_path, "wb"); - if (!f) { - fclose(tmp); - return NULL; - } - fwrite(pub_key, 1, pub_key_len, f); - fclose(f); - fclose(tmp); - - /* Initialize hybrid encryption */ + /* Initialize hybrid encryption directly from the in-memory public key. + * No key material is ever written to disk. */ zupt_keyring_t kr = {}; - int ret = zupt_hybrid_encrypt_init(&kr, tmp_path, enc_header, enc_header_len); - unlink(tmp_path); - + int ret = zupt_hybrid_encrypt_init_mem(&kr, pub_key, pub_key_len, + enc_header, enc_header_len); if (ret != 0) { return NULL; } @@ -374,24 +237,12 @@ uint8_t* zupt_hybrid_decrypt(const uint8_t* priv_key, size_t priv_key_len, return NULL; } - /* Create temporary private key file */ - char tmp_path[64]; - snprintf(tmp_path, sizeof(tmp_path), "/tmp/zupt_priv_%d", getpid()); - FILE* f = fopen(tmp_path, "wb"); - if (!f) return NULL; - - if (fwrite(priv_key, 1, priv_key_len, f) != priv_key_len) { - fclose(f); - unlink(tmp_path); - return NULL; - } - fclose(f); - - /* Initialize hybrid decryption */ + /* Initialize hybrid decryption directly from the in-memory private key. + * The secret key is never staged on disk (which would defeat the + * in-RAM mlock protection and leave key material in /tmp). */ zupt_keyring_t kr = {}; - int ret = zupt_hybrid_decrypt_init(&kr, tmp_path, enc_header, enc_header_len); - unlink(tmp_path); - + int ret = zupt_hybrid_decrypt_init_mem(&kr, priv_key, priv_key_len, + enc_header, enc_header_len); if (ret != 0) { return NULL; } @@ -410,11 +261,14 @@ uint8_t* zupt_hybrid_decrypt(const uint8_t* priv_key, size_t priv_key_len, return NULL; } - /* Read payload length from ciphertext (little-endian) */ - size_t block_len = ciphertext[pos] | - (ciphertext[pos + 1] << 8) | - (ciphertext[pos + 2] << 16) | - (ciphertext[pos + 3] << 24); + /* Read payload length from ciphertext (little-endian). + * Cast to uint32_t before shifting: a bare uint8_t promotes to int, + * so `byte << 24` is undefined/sign-extends when the top bit is set, + * which would yield a bogus (huge) block_len. */ + size_t block_len = (size_t)ciphertext[pos] | + ((size_t)ciphertext[pos + 1] << 8) | + ((size_t)ciphertext[pos + 2] << 16) | + ((size_t)ciphertext[pos + 3] << 24); if (block_len == 0) break;