// SPDX-License-Identifier: MIT
// sign/Verifier.cpp
#include "sign/Verifier.hh"
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <climits>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <mutex>
#include <sstream>
#include <string>
#include <unordered_map>
namespace fs = std::filesystem;
namespace fedem {
namespace sign {
// ─────────────────────────────────────────────────────────────────────────────
// Process-wide verify cache
// ─────────────────────────────────────────────────────────────────────────────
namespace {
struct VerifyCache
{
std::mutex mtx;
std::unordered_map<std::string, VerifyResult> map;
};
VerifyCache& cache()
{
static VerifyCache s;
return s;
}
} // namespace
// ─────────────────────────────────────────────────────────────────────────────
// Internal helpers
// ─────────────────────────────────────────────────────────────────────────────
static std::string hexEncode( const unsigned char* data, std::size_t len )
{
std::ostringstream ss;
ss << std::hex << std::setfill('0');
for( std::size_t i = 0; i < len; ++i )
ss << std::setw(2) << static_cast<unsigned>(data[i]);
return ss.str();
}
static std::string sha256File( std::string const& path )
{
std::ifstream f( path, std::ios::binary );
if( !f ) return "";
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex( ctx, EVP_sha256(), nullptr );
char buf[65536];
while( f.read( buf, sizeof buf ) || f.gcount() > 0 )
EVP_DigestUpdate( ctx, buf, static_cast<std::size_t>( f.gcount() ) );
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digestLen = 0;
EVP_DigestFinal_ex( ctx, digest, &digestLen );
EVP_MD_CTX_free( ctx );
return hexEncode( digest, digestLen );
}
static std::string pubKeyFingerprint( EVP_PKEY* pkey )
{
unsigned char* der = nullptr;
int len = i2d_PUBKEY( pkey, &der );
if( len <= 0 ) return "";
std::size_t dlen = 0;
unsigned char digest[EVP_MAX_MD_SIZE];
EVP_Q_digest( nullptr, "SHA256", nullptr,
der, static_cast<std::size_t>(len), digest, &dlen );
OPENSSL_free( der );
return hexEncode( digest, dlen );
}
// Strip .dso_manifest and .dso_sig sections → temp copy → caller removes it.
static std::string makeStrippedCopy( std::string const& soPath )
{
std::string tmp = soPath + ".verify-stripped.tmp";
std::error_code ec;
fs::copy_file( soPath, tmp, fs::copy_options::overwrite_existing, ec );
if( ec ) return "";
std::string cmd =
"objcopy --remove-section " + std::string(kManifestSection) +
" --remove-section " + std::string(kSigSection) +
" \"" + tmp + "\" 2>/dev/null";
std::system( cmd.c_str() );
return tmp;
}
// Find a *.pub in keysDir whose fingerprint matches keyId.
// Returns EVP_PKEY* (caller owns) or nullptr.
static EVP_PKEY* findTrustedKey( std::string const& keysDir,
std::string const& keyId )
{
if( keysDir.empty() || keyId.empty() ) return nullptr;
std::error_code ec;
if( !fs::exists( keysDir, ec ) ) return nullptr;
for( auto const& e : fs::directory_iterator( keysDir, ec ) )
{
if( ec ) break;
if( e.path().extension() != ".pub" ) continue;
FILE* f = std::fopen( e.path().c_str(), "r" );
if( !f ) continue;
EVP_PKEY* pk = PEM_read_PUBKEY( f, nullptr, nullptr, nullptr );
std::fclose( f );
if( !pk ) continue;
if( pubKeyFingerprint(pk) == keyId ) return pk;
EVP_PKEY_free( pk );
}
return nullptr;
}
static bool verifySignature( EVP_PKEY* pkey,
std::string const& json,
std::array<uint8_t,64> const& sig )
{
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
if( EVP_DigestVerifyInit( ctx, nullptr, nullptr, nullptr, pkey ) <= 0 )
{ EVP_MD_CTX_free( ctx ); return false; }
auto const* msg = reinterpret_cast<const unsigned char*>( json.data() );
int ok = EVP_DigestVerify( ctx, sig.data(), ED25519_SIG_BYTES,
msg, json.size() );
EVP_MD_CTX_free( ctx );
return ok == 1;
}
// ─────────────────────────────────────────────────────────────────────────────
// Verifier::isCached
// ─────────────────────────────────────────────────────────────────────────────
bool Verifier::isCached( std::string const& soPath )
{
std::lock_guard<std::mutex> lk( cache().mtx );
return cache().map.count( soPath ) > 0;
}
// ─────────────────────────────────────────────────────────────────────────────
// Verifier::verify (cache wrapper)
// ─────────────────────────────────────────────────────────────────────────────
VerifyResult Verifier::verify( std::string const& soPath,
std::string const& trustedKeysDir )
{
{
std::lock_guard<std::mutex> lk( cache().mtx );
auto it = cache().map.find( soPath );
if( it != cache().map.end() ) return it->second;
}
VerifyResult result = verifyUncached( soPath, trustedKeysDir );
{
std::lock_guard<std::mutex> lk( cache().mtx );
cache().map.emplace( soPath, result );
}
return result;
}
// ─────────────────────────────────────────────────────────────────────────────
// Verifier::verifyUncached
// ─────────────────────────────────────────────────────────────────────────────
VerifyResult Verifier::verifyUncached( std::string const& soPath,
std::string const& trustedKeysDir )
{
VerifyResult result;
// 1. Read ELF sections
ManifestResult mr = Manifest::read( soPath );
if( !mr.error.empty() )
{
result.trust = TrustLevel::REJECTED;
result.detail = "manifest read error: " + mr.error;
return result;
}
if( !mr.hasManifest && !mr.hasSig )
{ result.trust = TrustLevel::UNSIGNED; result.detail = "no manifest sections"; return result; }
if( mr.hasManifest && !mr.hasSig )
{
result.trust = TrustLevel::UNSIGNED;
result.hasManifest = true; result.data = mr.data;
result.detail = "manifest present but no signature";
return result;
}
if( !mr.hasManifest && mr.hasSig )
{ result.trust = TrustLevel::REJECTED; result.detail = "signature without manifest"; return result; }
result.hasManifest = true;
result.data = mr.data;
// 2. SHA-256 of stripped DSO
std::string const stripped = makeStrippedCopy( soPath );
if( stripped.empty() )
{ result.trust = TrustLevel::REJECTED; result.detail = "cannot create stripped copy"; return result; }
std::string const actualHash = sha256File( stripped );
fs::remove( stripped );
if( actualHash != mr.data.sha256 )
{
result.trust = TrustLevel::REJECTED;
result.detail = "SHA-256 mismatch — manifest: " + mr.data.sha256
+ " actual: " + actualHash;
return result;
}
// 3. Find trusted key
EVP_PKEY* trustedKey = findTrustedKey( trustedKeysDir, mr.data.publicKeyId );
if( !trustedKey )
{
result.trust = TrustLevel::UNKNOWN;
result.detail = "key not in trusted-keys directory (" + mr.data.publicKeyId + ")";
return result;
}
// 4. Ed25519 verify
bool const ok = verifySignature( trustedKey, mr.manifestJson, mr.sig );
EVP_PKEY_free( trustedKey );
if( !ok )
{ result.trust = TrustLevel::REJECTED; result.detail = "Ed25519 signature invalid"; return result; }
result.trust = TrustLevel::TRUSTED;
result.detail = "trusted key: " + mr.data.publicKeyId;
return result;
}
// ─────────────────────────────────────────────────────────────────────────────
// Verifier::shouldLoad
// ─────────────────────────────────────────────────────────────────────────────
bool Verifier::shouldLoad( std::string const& policy,
TrustLevel level ) noexcept
{
if( policy == "off" || policy.empty() ) return true;
if( level == TrustLevel::REJECTED ) return false;
if( policy == "strict" ) return level == TrustLevel::TRUSTED;
return true; // "warn"
}
} // namespace sign
} // namespace fedem