|
1 | 1 | #include "OpenSSLSigner.h" |
2 | 2 |
|
3 | | -#include <memory> |
| 3 | +#include <algorithm> |
| 4 | +#include <cstring> |
| 5 | +#include <sodium.h> |
4 | 6 | #include <stdexcept> |
5 | 7 | #include <string_view> |
| 8 | +#include <vector> |
6 | 9 |
|
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; |
15 | 11 |
|
16 | 12 | namespace { |
17 | | -void OpenSSLThrow(std::string_view msgUser) |
| 13 | + |
| 14 | +std::vector<unsigned char> Base64Decode(std::string_view input) |
18 | 15 | { |
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); |
29 | 22 | } |
30 | | - throw std::runtime_error(fmt::format("{}: openssl: {}", msgUser, msgSsl)); |
31 | | -} |
32 | 23 |
|
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"); |
40 | 32 | } |
41 | | - return OpenSSLSignerImpl::UniquePtrWithDeleter<T>( |
42 | | - ptr, OpenSSLSignerImpl::Deleter<T>{ cb }); |
| 33 | + out.resize(bin_len); |
| 34 | + return out; |
43 | 35 | } |
44 | 36 |
|
45 | | -std::string Base64Encode(const unsigned char* data, size_t dataLen) |
| 37 | +std::vector<unsigned char> ParseEd25519Pem(const std::string& pkeyPem) |
46 | 38 | { |
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."); |
53 | 69 | } |
54 | | - buf.resize(static_cast<size_t>(realLen)); |
55 | | - return buf; |
56 | 70 | } |
| 71 | + |
57 | 72 | } // namespace |
58 | 73 |
|
59 | 74 | OpenSSLPrivateKey::OpenSSLPrivateKey(const std::string& pkeyPem) |
60 | 75 | { |
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()); |
66 | 90 | } |
67 | 91 |
|
68 | 92 | OpenSSLSigner::OpenSSLSigner(std::shared_ptr<OpenSSLPrivateKey> pkey_) |
69 | 93 | : pkey(pkey_) |
70 | | - , sslMdCtx(OpenSSLPtrWrap(EVP_MD_CTX_new(), EVP_MD_CTX_free, |
71 | | - "could not create MD_CTX")) |
72 | 94 | { |
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"); |
77 | 97 | } |
78 | 98 |
|
79 | 99 | std::string OpenSSLSigner::SignB64(const unsigned char* data, size_t len) |
80 | 100 | { |
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"); |
89 | 118 | } |
90 | | - return Base64Encode(sig.data(), sig.size()); |
| 119 | + |
| 120 | + b64.resize(strlen(b64.c_str())); |
| 121 | + |
| 122 | + return b64; |
91 | 123 | } |
0 commit comments