/* * Ed25519Utils.cpp — Ed25519 cryptographic primitives (OpenSSL 3.0 EVP API). * * Copyright (c) 2026 Are Bjørby * SPDX-License-Identifier: MIT */ #include "Ed25519Utils.hpp" #include #include #include #include #include #include #include namespace antpeer { namespace crypto { /* ── Hex utilities ────────────────────────────────────────────────── */ static const char hex_table[] = "0123456789abcdef"; std::string to_hex(const uint8_t* data, size_t len) { std::string out; out.reserve(len * 2); for (size_t i = 0; i < len; ++i) { out += hex_table[(data[i] >> 4) & 0x0f]; out += hex_table[data[i] & 0x0f]; } return out; } static int hex_digit(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -1; } bool from_hex(const std::string& hex, uint8_t* out, size_t max_len) { if (hex.size() % 2 != 0) return false; size_t byte_len = hex.size() / 2; if (byte_len > max_len) return false; for (size_t i = 0; i < byte_len; ++i) { int hi = hex_digit(hex[i * 2]); int lo = hex_digit(hex[i * 2 + 1]); if (hi < 0 || lo < 0) return false; out[i] = static_cast((hi << 4) | lo); } return true; } /* ── RAII helpers ─────────────────────────────────────────────────── */ struct EvpKeyDeleter { void operator()(EVP_PKEY* p) const { if (p) EVP_PKEY_free(p); } }; using EvpKeyPtr = std::unique_ptr; struct EvpCtxDeleter { void operator()(EVP_PKEY_CTX* p) const { if (p) EVP_PKEY_CTX_free(p); } }; using EvpCtxPtr = std::unique_ptr; struct MdCtxDeleter { void operator()(EVP_MD_CTX* p) const { if (p) EVP_MD_CTX_free(p); } }; using MdCtxPtr = std::unique_ptr; /* ── Private key → EVP_PKEY ───────────────────────────────────────── */ static EvpKeyPtr pkey_from_private(const std::string& private_key_hex) { uint8_t seed[32]; if (!from_hex(private_key_hex, seed, sizeof(seed)) || private_key_hex.size() != 64) throw std::invalid_argument("invalid private key hex (need 64 chars)"); EVP_PKEY* raw = EVP_PKEY_new_raw_private_key( EVP_PKEY_ED25519, nullptr, seed, 32); if (!raw) throw std::runtime_error("EVP_PKEY_new_raw_private_key failed"); return EvpKeyPtr(raw); } /* ── Public key → EVP_PKEY ────────────────────────────────────────── */ static EvpKeyPtr pkey_from_public(const std::string& public_key_hex) { uint8_t pub[32]; if (!from_hex(public_key_hex, pub, sizeof(pub)) || public_key_hex.size() != 64) throw std::invalid_argument("invalid public key hex (need 64 chars)"); EVP_PKEY* raw = EVP_PKEY_new_raw_public_key( EVP_PKEY_ED25519, nullptr, pub, 32); if (!raw) throw std::runtime_error("EVP_PKEY_new_raw_public_key failed"); return EvpKeyPtr(raw); } /* ── Key generation ───────────────────────────────────────────────── */ KeyPair keygen() { EvpCtxPtr ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr)); if (!ctx) throw std::runtime_error("EVP_PKEY_CTX_new_id failed"); if (EVP_PKEY_keygen_init(ctx.get()) <= 0) throw std::runtime_error("EVP_PKEY_keygen_init failed"); EVP_PKEY* pkey = nullptr; if (EVP_PKEY_keygen(ctx.get(), &pkey) <= 0) throw std::runtime_error("EVP_PKEY_keygen failed"); EvpKeyPtr key(pkey); uint8_t priv[32], pub[32]; size_t priv_len = 32, pub_len = 32; if (!EVP_PKEY_get_raw_private_key(key.get(), priv, &priv_len)) throw std::runtime_error("get_raw_private_key failed"); if (!EVP_PKEY_get_raw_public_key(key.get(), pub, &pub_len)) throw std::runtime_error("get_raw_public_key failed"); /* Random key_id: 16 bytes → 32 hex chars */ uint8_t kid[16]; if (RAND_bytes(kid, sizeof(kid)) != 1) throw std::runtime_error("RAND_bytes failed"); return KeyPair{to_hex(priv, priv_len), to_hex(pub, pub_len), to_hex(kid, sizeof(kid))}; } /* ── Sign ─────────────────────────────────────────────────────────── */ std::string sign(const void* data, size_t len, const std::string& private_key_hex) { EvpKeyPtr pkey = pkey_from_private(private_key_hex); MdCtxPtr mdctx(EVP_MD_CTX_new()); if (!mdctx) throw std::runtime_error("EVP_MD_CTX_new failed"); if (EVP_DigestSignInit(mdctx.get(), nullptr, nullptr, nullptr, pkey.get()) <= 0) throw std::runtime_error("EVP_DigestSignInit failed"); /* First call: get signature length */ size_t siglen = 0; if (EVP_DigestSign(mdctx.get(), nullptr, &siglen, static_cast(data), len) <= 0) throw std::runtime_error("EVP_DigestSign (size) failed"); uint8_t sig[64]; siglen = sizeof(sig); if (EVP_DigestSign(mdctx.get(), sig, &siglen, static_cast(data), len) <= 0) throw std::runtime_error("EVP_DigestSign failed"); return to_hex(sig, siglen); } std::string sign(std::string_view data, const std::string& private_key_hex) { return sign(data.data(), data.size(), private_key_hex); } /* ── Verify ───────────────────────────────────────────────────────── */ bool verify(const void* data, size_t len, const std::string& signature_hex, const std::string& public_key_hex) { uint8_t sig[64]; if (!from_hex(signature_hex, sig, sizeof(sig)) || signature_hex.size() != 128) return false; EvpKeyPtr pkey = pkey_from_public(public_key_hex); MdCtxPtr mdctx(EVP_MD_CTX_new()); if (!mdctx) return false; if (EVP_DigestVerifyInit(mdctx.get(), nullptr, nullptr, nullptr, pkey.get()) <= 0) return false; int rc = EVP_DigestVerify(mdctx.get(), sig, sizeof(sig), static_cast(data), len); return rc == 1; } bool verify(std::string_view data, const std::string& signature_hex, const std::string& public_key_hex) { return verify(data.data(), data.size(), signature_hex, public_key_hex); } /* ── Nonce ────────────────────────────────────────────────────────── */ std::string nonce(size_t bytes) { if (bytes == 0 || bytes > 256) throw std::invalid_argument("nonce: bytes must be 1-256"); uint8_t buf[256]; if (RAND_bytes(buf, static_cast(bytes)) != 1) throw std::runtime_error("RAND_bytes failed"); return to_hex(buf, bytes); } /* ── Key I/O ──────────────────────────────────────────────────────── */ void save_key(const KeyPair& kp, const char* path) { FILE* f = fopen(path, "w"); if (!f) throw std::runtime_error(std::string("cannot open ") + path + ": " + strerror(errno)); fprintf(f, "private_key = %s\n", kp.private_key.c_str()); fprintf(f, "public_key = %s\n", kp.public_key.c_str()); fprintf(f, "key_id = %s\n", kp.key_id.c_str()); fclose(f); } static std::string parse_value(const std::string& line) { size_t eq = line.find('='); if (eq == std::string::npos) return {}; std::string val = line.substr(eq + 1); size_t start = val.find_first_not_of(" \t"); if (start == std::string::npos) return {}; size_t end = val.find_last_not_of(" \t\r\n"); return val.substr(start, end - start + 1); } static std::string parse_key_name(const std::string& line) { size_t eq = line.find('='); if (eq == std::string::npos) return {}; std::string key = line.substr(0, eq); size_t end = key.find_last_not_of(" \t"); if (end == std::string::npos) return {}; return key.substr(0, end + 1); } KeyPair load_key(const char* path) { FILE* f = fopen(path, "r"); if (!f) throw std::runtime_error(std::string("cannot open ") + path + ": " + strerror(errno)); KeyPair kp; char line[512]; while (fgets(line, sizeof(line), f)) { std::string s(line); std::string key = parse_key_name(s); std::string val = parse_value(s); if (key == "private_key") kp.private_key = val; else if (key == "public_key") kp.public_key = val; else if (key == "key_id") kp.key_id = val; } fclose(f); if (kp.private_key.empty() || kp.public_key.empty() || kp.key_id.empty()) throw std::runtime_error(std::string("incomplete key file: ") + path); return kp; } /* ── Trusted key store ────────────────────────────────────────────── */ std::vector load_trusted_keys(const char* path) { FILE* f = fopen(path, "r"); if (!f) throw std::runtime_error(std::string("cannot open ") + path + ": " + strerror(errno)); std::vector keys; char line[512]; while (fgets(line, sizeof(line), f)) { std::string s(line); /* Strip trailing whitespace */ while (!s.empty() && (s.back() == '\n' || s.back() == '\r' || s.back() == ' ' || s.back() == '\t')) s.pop_back(); if (s.empty() || s[0] == '#') continue; /* Parse: OID key_id public_key_hex */ size_t p1 = s.find_first_of(" \t"); if (p1 == std::string::npos) continue; size_t p2 = s.find_first_not_of(" \t", p1); if (p2 == std::string::npos) continue; size_t p3 = s.find_first_of(" \t", p2); if (p3 == std::string::npos) continue; size_t p4 = s.find_first_not_of(" \t", p3); if (p4 == std::string::npos) continue; TrustedKey tk; tk.oid = s.substr(0, p1); tk.key_id = s.substr(p2, p3 - p2); tk.public_key = s.substr(p4); /* Strip trailing whitespace from public_key */ while (!tk.public_key.empty() && (tk.public_key.back() == ' ' || tk.public_key.back() == '\t')) tk.public_key.pop_back(); if (tk.public_key.size() == 64) keys.push_back(std::move(tk)); } fclose(f); return keys; } const TrustedKey* find_trusted_key(const std::vector& keys, std::string_view oid, std::string_view key_id) { if (oid.empty() && key_id.empty()) return nullptr; for (auto& k : keys) { if (!oid.empty() && k.oid != oid) continue; if (!key_id.empty() && k.key_id != key_id) continue; return &k; } return nullptr; } } // namespace crypto } // namespace antpeer