125 lines
4.5 KiB
C++
125 lines
4.5 KiB
C++
// net 层 RSA 加密器测试(OpenSSL 3.x EVP API)。
|
||
// 用临时生成的 RSA-2048 密钥对自测,不依赖实站公钥:
|
||
// 1) 用 EVP_RSA_gen(2048) 生成密钥对;
|
||
// 2) 导出公钥 PEM,喂给 RsaEncryptor 加密 + base64;
|
||
// 3) 用同一密钥对的私钥解密密文,断言能还原原文。
|
||
// PKCS#1 v1.5 填充与 JSEncrypt 默认一致。
|
||
|
||
#include <gtest/gtest.h>
|
||
|
||
#include <memory>
|
||
#include <stdexcept>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#include <openssl/bio.h>
|
||
#include <openssl/evp.h>
|
||
#include <openssl/pem.h>
|
||
#include <openssl/rsa.h>
|
||
|
||
#include "crypto/RsaEncryptor.hpp"
|
||
|
||
namespace {
|
||
|
||
// RAII 包装:保证测试里 OpenSSL 句柄不泄漏。
|
||
struct PkeyDeleter {
|
||
void operator()(EVP_PKEY* p) const noexcept {
|
||
if (p) EVP_PKEY_free(p);
|
||
}
|
||
};
|
||
struct PkeyCtxDeleter {
|
||
void operator()(EVP_PKEY_CTX* c) const noexcept {
|
||
if (c) EVP_PKEY_CTX_free(c);
|
||
}
|
||
};
|
||
struct BioDeleter {
|
||
void operator()(BIO* b) const noexcept {
|
||
if (b) BIO_free(b);
|
||
}
|
||
};
|
||
|
||
using PkeyPtr = std::unique_ptr<EVP_PKEY, PkeyDeleter>;
|
||
using PkeyCtxPtr = std::unique_ptr<EVP_PKEY_CTX, PkeyCtxDeleter>;
|
||
using BioPtr = std::unique_ptr<BIO, BioDeleter>;
|
||
|
||
// base64 解码(处理尾部 padding),用于长度断言。
|
||
std::vector<unsigned char> base64Decode(const std::string& b64) {
|
||
std::vector<unsigned char> out(b64.size()); // 解码后必不超过输入长度
|
||
int decoded = EVP_DecodeBlock(out.data(),
|
||
reinterpret_cast<const unsigned char*>(b64.data()),
|
||
static_cast<int>(b64.size()));
|
||
if (decoded < 0) return {};
|
||
// EVP_DecodeBlock 按 4 字节对齐解码,会把 '=' 当作 0;需按 padding 数扣除尾部字节。
|
||
size_t pad = 0;
|
||
if (!b64.empty() && b64[b64.size() - 1] == '=') ++pad;
|
||
if (b64.size() >= 2 && b64[b64.size() - 2] == '=') ++pad;
|
||
out.resize(static_cast<size_t>(decoded) - pad);
|
||
return out;
|
||
}
|
||
|
||
// 用私钥(kp)以 PKCS#1 v1.5 解密 cipher。
|
||
std::string decryptWithPrivateKey(EVP_PKEY* kp, const std::vector<unsigned char>& cipher) {
|
||
PkeyCtxPtr ctx(EVP_PKEY_CTX_new(kp, nullptr));
|
||
EXPECT_NE(ctx, nullptr);
|
||
EXPECT_GT(EVP_PKEY_decrypt_init(ctx.get()), 0);
|
||
EXPECT_GT(EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PADDING), 0);
|
||
|
||
size_t outlen = 0;
|
||
EXPECT_GT(EVP_PKEY_decrypt(ctx.get(), nullptr, &outlen, cipher.data(), cipher.size()), 0);
|
||
std::vector<unsigned char> plain(outlen);
|
||
EXPECT_GT(EVP_PKEY_decrypt(ctx.get(), plain.data(), &outlen, cipher.data(), cipher.size()), 0);
|
||
return std::string(reinterpret_cast<const char*>(plain.data()), outlen);
|
||
}
|
||
|
||
std::string exportPublicKeyPem(EVP_PKEY* kp) {
|
||
BioPtr bio(BIO_new(BIO_s_mem()));
|
||
EXPECT_NE(bio, nullptr);
|
||
EXPECT_GT(PEM_write_bio_PUBKEY(bio.get(), kp), 0);
|
||
char* data = nullptr;
|
||
long len = BIO_get_mem_data(bio.get(), &data);
|
||
return std::string(data, static_cast<size_t>(len));
|
||
}
|
||
|
||
} // namespace
|
||
|
||
TEST(RsaEncryptorTest, EncryptsToBase64DecryptableByPrivateKey) {
|
||
// Arrange: 临时生成 RSA-2048 密钥对,导出公钥 PEM。
|
||
PkeyPtr kp(EVP_RSA_gen(2048));
|
||
ASSERT_NE(kp, nullptr);
|
||
const std::string pubPem = exportPublicKeyPem(kp.get());
|
||
ASSERT_FALSE(pubPem.empty());
|
||
|
||
const std::string plaintext = "hello-geopro";
|
||
|
||
// Act: 加密 + base64。
|
||
geopro::net::RsaEncryptor enc(pubPem);
|
||
const std::string b64 = enc.encryptBase64(plaintext);
|
||
|
||
// Assert: base64 解码后是 256 字节密文(RSA-2048)。
|
||
const std::vector<unsigned char> cipher = base64Decode(b64);
|
||
EXPECT_EQ(cipher.size(), 256u);
|
||
|
||
// Assert: 私钥能解出原文(即便长度断言放宽,这条是核心正确性保证)。
|
||
const std::string recovered = decryptWithPrivateKey(kp.get(), cipher);
|
||
EXPECT_EQ(recovered, plaintext);
|
||
}
|
||
|
||
TEST(RsaEncryptorTest, ThrowsOnInvalidPublicKey) {
|
||
EXPECT_THROW(geopro::net::RsaEncryptor("not-a-valid-pem"), std::runtime_error);
|
||
}
|
||
|
||
TEST(RsaEncryptorTest, MoveConstructionPreservesEncryption) {
|
||
PkeyPtr kp(EVP_RSA_gen(2048));
|
||
ASSERT_NE(kp, nullptr);
|
||
const std::string pubPem = exportPublicKeyPem(kp.get());
|
||
|
||
geopro::net::RsaEncryptor original(pubPem);
|
||
geopro::net::RsaEncryptor moved(std::move(original));
|
||
|
||
const std::string plaintext = "move-check";
|
||
const std::string b64 = moved.encryptBase64(plaintext);
|
||
const std::vector<unsigned char> cipher = base64Decode(b64);
|
||
EXPECT_EQ(cipher.size(), 256u);
|
||
EXPECT_EQ(decryptWithPrivateKey(kp.get(), cipher), plaintext);
|
||
}
|