Skip to content

Commit 842cfef

Browse files
authored
fix(skymp5-server): fix OpenSSL conflicts by rewriting in libsodium (skyrim-multiplayer#2608)
1 parent 9e7f721 commit 842cfef

File tree

6 files changed

+116
-108
lines changed

6 files changed

+116
-108
lines changed

skymp5-server/cpp/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ if(NOT EMSCRIPTEN)
108108
find_package(mongoc-1.0 CONFIG REQUIRED)
109109
endif()
110110

111-
find_package(OpenSSL REQUIRED)
111+
find_package(unofficial-sodium CONFIG REQUIRED)
112112

113113
# MacOS not supported by rsm-bsa
114114
if(NOT APPLE AND NOT EMSCRIPTEN)
@@ -137,7 +137,7 @@ foreach(target ${VCPKG_DEPENDENT})
137137
target_compile_definitions(${target} PUBLIC NO_MONGO=1)
138138
endif()
139139

140-
target_link_libraries(${target} PUBLIC OpenSSL::SSL OpenSSL::Crypto)
140+
target_link_libraries(${target} PUBLIC unofficial-sodium::sodium)
141141

142142
if(TARGET bsa::bsa)
143143
target_link_libraries(${target} PUBLIC bsa::bsa)
Lines changed: 92 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,123 @@
11
#include "OpenSSLSigner.h"
22

3-
#include <memory>
3+
#include <algorithm>
4+
#include <cstring>
5+
#include <sodium.h>
46
#include <stdexcept>
57
#include <string_view>
8+
#include <vector>
69

7-
#include <antigo/Context.h>
8-
#include <openssl/bio.h>
9-
#include <openssl/err.h>
10-
#include <openssl/evp.h>
11-
#include <openssl/pem.h>
12-
#include <spdlog/spdlog.h>
13-
14-
using namespace std::string_view_literals;
10+
using namespace std::string_literals;
1511

1612
namespace {
17-
void OpenSSLThrow(std::string_view msgUser)
13+
14+
std::vector<unsigned char> Base64Decode(std::string_view input)
1815
{
19-
ANTIGO_CONTEXT_INIT(ctx);
20-
std::string msgSsl;
21-
auto callback = [](const char* str, size_t len, void* u) -> int {
22-
auto& msgSsl = *reinterpret_cast<std::string*>(u);
23-
msgSsl.insert(msgSsl.end(), str, str + len);
24-
return len;
25-
};
26-
ERR_print_errors_cb(callback, &msgSsl);
27-
while (!msgSsl.empty() && msgSsl.ends_with('\n')) {
28-
msgSsl.pop_back();
16+
// Remove newlines if any
17+
std::string clean;
18+
clean.reserve(input.size());
19+
for (char c : input) {
20+
if (!isspace(c))
21+
clean.push_back(c);
2922
}
30-
throw std::runtime_error(fmt::format("{}: openssl: {}", msgUser, msgSsl));
31-
}
3223

33-
template <class T, class F>
34-
auto OpenSSLPtrWrap(T* ptr, F cb,
35-
std::string_view msg = "init returned null pointer"sv)
36-
{
37-
ANTIGO_CONTEXT_INIT(ctx);
38-
if (ptr == nullptr) {
39-
OpenSSLThrow(msg);
24+
size_t required_len = clean.size() / 4 * 3; // Estimate
25+
std::vector<unsigned char> out(required_len + 10);
26+
size_t bin_len;
27+
28+
if (sodium_base642bin(out.data(), out.size(), clean.c_str(), clean.size(),
29+
nullptr, &bin_len, nullptr,
30+
sodium_base64_VARIANT_ORIGINAL) != 0) {
31+
throw std::runtime_error("Base64 decode failed");
4032
}
41-
return OpenSSLSignerImpl::UniquePtrWithDeleter<T>(
42-
ptr, OpenSSLSignerImpl::Deleter<T>{ cb });
33+
out.resize(bin_len);
34+
return out;
4335
}
4436

45-
std::string Base64Encode(const unsigned char* data, size_t dataLen)
37+
std::vector<unsigned char> ParseEd25519Pem(const std::string& pkeyPem)
4638
{
47-
size_t encLen = EVP_ENCODE_LENGTH(dataLen);
48-
std::string buf(encLen, '\0');
49-
int realLen = EVP_EncodeBlock(reinterpret_cast<unsigned char*>(buf.data()),
50-
data, dataLen);
51-
if (realLen <= 0) {
52-
OpenSSLThrow("base64 encode failed");
39+
// Basic PEM parser for "PRIVATE KEY" (PKCS#8 for Ed25519)
40+
// We expect header/footer and Base64 content.
41+
42+
std::string_view header = "-----BEGIN PRIVATE KEY-----";
43+
std::string_view footer = "-----END PRIVATE KEY-----";
44+
45+
auto start = pkeyPem.find(header);
46+
if (start == std::string::npos)
47+
throw std::runtime_error("Missing PEM header");
48+
start += header.size();
49+
50+
auto end = pkeyPem.find(footer, start);
51+
if (end == std::string::npos)
52+
throw std::runtime_error("Missing PEM footer");
53+
54+
auto b64 = std::string_view(pkeyPem).substr(start, end - start);
55+
auto der = Base64Decode(b64);
56+
57+
// Parse PKCS#8 structure for Ed25519.
58+
// The structure typically involves an algorithm identifier and the key octet
59+
// string. For Ed25519, the private key seed is the last 32 bytes of the
60+
// 48-byte structure.
61+
62+
if (der.size() == 48) {
63+
return std::vector<unsigned char>(der.end() - 32, der.end());
64+
} else if (der.size() == 32) {
65+
return der;
66+
} else {
67+
throw std::runtime_error(
68+
"Unsupported PEM key size or format. Expected 48-byte PKCS#8 Ed25519.");
5369
}
54-
buf.resize(static_cast<size_t>(realLen));
55-
return buf;
5670
}
71+
5772
} // namespace
5873

5974
OpenSSLPrivateKey::OpenSSLPrivateKey(const std::string& pkeyPem)
6075
{
61-
auto bio = OpenSSLPtrWrap(BIO_new_mem_buf(pkeyPem.c_str(), pkeyPem.length()),
62-
BIO_free, "could not allocate io buffer for pkey");
63-
pkey = OpenSSLPtrWrap(
64-
PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr),
65-
EVP_PKEY_free, "could not parse pkey");
76+
static bool g_initStatus = (sodium_init() >= 0);
77+
if (!g_initStatus) {
78+
throw std::runtime_error("libsodium initialization failed");
79+
}
80+
81+
auto seed = ParseEd25519Pem(pkeyPem);
82+
if (seed.size() != crypto_sign_SEEDBYTES) {
83+
throw std::runtime_error("Invalid Ed25519 seed size");
84+
}
85+
86+
secretKey.resize(crypto_sign_SECRETKEYBYTES);
87+
std::vector<unsigned char> pk(crypto_sign_PUBLICKEYBYTES);
88+
89+
crypto_sign_seed_keypair(pk.data(), secretKey.data(), seed.data());
6690
}
6791

6892
OpenSSLSigner::OpenSSLSigner(std::shared_ptr<OpenSSLPrivateKey> pkey_)
6993
: pkey(pkey_)
70-
, sslMdCtx(OpenSSLPtrWrap(EVP_MD_CTX_new(), EVP_MD_CTX_free,
71-
"could not create MD_CTX"))
7294
{
73-
if (EVP_DigestSignInit(sslMdCtx.get(), nullptr, nullptr, nullptr,
74-
pkey->GetWrapped()) <= 0) {
75-
OpenSSLThrow("failed to init sign");
76-
}
95+
if (!pkey)
96+
throw std::runtime_error("pkey is null");
7797
}
7898

7999
std::string OpenSSLSigner::SignB64(const unsigned char* data, size_t len)
80100
{
81-
ANTIGO_CONTEXT_INIT(ctx);
82-
size_t sigLen;
83-
if (EVP_DigestSign(sslMdCtx.get(), nullptr, &sigLen, data, len) <= 0) {
84-
OpenSSLThrow("failed to get signature len");
85-
}
86-
std::vector<unsigned char> sig(sigLen);
87-
if (EVP_DigestSign(sslMdCtx.get(), sig.data(), &sigLen, data, len) <= 0) {
88-
OpenSSLThrow("failed to get signature");
101+
if (!pkey)
102+
throw std::runtime_error("pkey is null");
103+
104+
std::vector<unsigned char> sig(crypto_sign_BYTES);
105+
unsigned long long sigLen_out;
106+
107+
crypto_sign_detached(sig.data(), &sigLen_out, data, len,
108+
pkey->GetSecretKey().data());
109+
110+
// Base64 encode signature
111+
size_t b64maxlen = sodium_base64_ENCODED_LEN(crypto_sign_BYTES,
112+
sodium_base64_VARIANT_ORIGINAL);
113+
std::string b64(b64maxlen, '\0');
114+
115+
if (sodium_bin2base64(b64.data(), b64maxlen, sig.data(), sigLen_out,
116+
sodium_base64_VARIANT_ORIGINAL) == nullptr) {
117+
throw std::runtime_error("Base64 encode failed");
89118
}
90-
return Base64Encode(sig.data(), sig.size());
119+
120+
b64.resize(strlen(b64.c_str()));
121+
122+
return b64;
91123
}
Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,18 @@
11
#pragma once
22

33
#include <memory>
4-
#include <stdexcept>
54
#include <string>
6-
#include <type_traits>
7-
#include <variant>
8-
9-
// openssl
10-
typedef struct evp_pkey_st EVP_PKEY;
11-
typedef struct evp_md_ctx_st EVP_MD_CTX;
12-
13-
namespace OpenSSLSignerImpl {
14-
template <class T>
15-
struct Deleter
16-
{
17-
using FreeCbIntT = int (*)(T*);
18-
using FreeCbVoidT = void (*)(T*);
19-
std::variant<FreeCbIntT, FreeCbVoidT> freeCb;
20-
21-
void operator()(T* ptr) const
22-
{
23-
std::visit(
24-
[ptr](auto cb) {
25-
if constexpr (std::is_same_v<decltype(cb(ptr)), int>) {
26-
if (cb(ptr) <= 0) {
27-
throw std::runtime_error("dealloc failed");
28-
}
29-
} else {
30-
cb(ptr);
31-
}
32-
},
33-
freeCb);
34-
}
35-
};
36-
37-
template <class T>
38-
using UniquePtrWithDeleter = std::unique_ptr<T, Deleter<T>>;
39-
} // namespace OpenSSLSignerImpl
5+
#include <vector>
406

417
class OpenSSLPrivateKey
428
{
439
public:
4410
explicit OpenSSLPrivateKey(const std::string& pkeyPem);
4511

46-
EVP_PKEY* GetWrapped() { return pkey.get(); }
12+
const std::vector<unsigned char>& GetSecretKey() const { return secretKey; }
4713

4814
private:
49-
OpenSSLSignerImpl::UniquePtrWithDeleter<EVP_PKEY> pkey;
15+
std::vector<unsigned char> secretKey;
5016
};
5117

5218
class OpenSSLSigner
@@ -58,5 +24,4 @@ class OpenSSLSigner
5824

5925
private:
6026
std::shared_ptr<OpenSSLPrivateKey> pkey;
61-
OpenSSLSignerImpl::UniquePtrWithDeleter<EVP_MD_CTX> sslMdCtx;
6227
};

skymp5-server/cpp/server_guest_lib/database_drivers/MongoDatabase.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include <atomic>
1919
#include <map>
2020
#include <mutex>
21-
#include <openssl/sha.h>
21+
#include <sodium.h>
2222
#include <spdlog/spdlog.h>
2323
#include <thread>
2424
#include <vector>
@@ -321,9 +321,9 @@ std::string MongoDatabase::BytesToHexString(const uint8_t* bytes,
321321

322322
std::string MongoDatabase::Sha256(const std::string& str)
323323
{
324-
uint8_t hash[SHA256_DIGEST_LENGTH];
325-
SHA256(reinterpret_cast<const uint8_t*>(str.data()), str.size(), hash);
326-
return BytesToHexString(hash, SHA256_DIGEST_LENGTH);
324+
unsigned char hash[crypto_hash_sha256_BYTES];
325+
crypto_hash_sha256(hash, reinterpret_cast<const unsigned char*>(str.data()), str.size());
326+
return BytesToHexString(hash, crypto_hash_sha256_BYTES);
327327
}
328328

329329
#endif // #ifndef NO_MONGO

unit/MongoDatabaseTest.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#include "TestUtils.hpp"
33
#include <catch2/catch_test_macros.hpp>
44
#include <nlohmann/json.hpp>
5-
#include <openssl/sha.h>
5+
#include <sodium.h>
66
#include <simdjson.h>
77

88
namespace MongoDatabaseTestUtils {
@@ -21,9 +21,9 @@ std::string BytesToHexString(const uint8_t* bytes, size_t length)
2121

2222
std::string Sha256(const std::string& str)
2323
{
24-
uint8_t hash[SHA256_DIGEST_LENGTH];
25-
SHA256(reinterpret_cast<const uint8_t*>(str.data()), str.size(), hash);
26-
return BytesToHexString(hash, SHA256_DIGEST_LENGTH);
24+
unsigned char hash[crypto_hash_sha256_BYTES];
25+
crypto_hash_sha256(hash, reinterpret_cast<const unsigned char*>(str.data()), str.size());
26+
return BytesToHexString(hash, crypto_hash_sha256_BYTES);
2727
}
2828
}
2929

unit/OpenSSLSignerTest.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,14 @@ MC4CAQAwBQYDK2VwBCIEIILh6zzNPCOMUWMvW4QXXXPPWbyJoNL8ggkqiD+2mZdp
2828
"2Walov9ocK6JYClLhF3DhJ49yBQ==");
2929
}
3030
}
31+
32+
TEST_CASE("OpenSSLSigner Invalid Key Size")
33+
{
34+
std::string badPem = "-----BEGIN PRIVATE KEY-----\n"
35+
"AAAA\n"
36+
"-----END PRIVATE KEY-----";
37+
38+
REQUIRE_THROWS_WITH(
39+
std::make_shared<OpenSSLPrivateKey>(badPem),
40+
"Unsupported PEM key size or format. Expected 48-byte PKCS#8 Ed25519.");
41+
}

0 commit comments

Comments
 (0)