Code View

plugin / plugin-2.2.0.0 / src / sign / Verifier.cpp
// 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