Skip to content

Latest commit

 

History

History

README.md

HashValidation

Demo only — The unsigned plugins.json can be tampered with freely (replace hash + BPL together). For production, sign the manifest or embed hashes in a signed executable.

Hash-based plugin validation via manifest. Plugins are only loaded if their SHA256 hash matches the manifest.

Architecture

HashValidationApp.exe
  |
  +-- DCC_UsePackage = rtl;fmx;FMXPluginFramework
  |
  +-- TManifestValidator.Create(plugins.json)
  |     reads sha256 fields from manifest
  |
  +-- FLoader.OnValidateModule := FManifest.ValidateModule
  |     callback checks SHA256 hash before LoadPackage
  |
  +-- SignedPlugin.bpl
  +-- UnsignedOptional.bpl
  +-- UnsignedRequired.bpl

What This Demo Shows

  • TManifestValidator from Plugin.Manifest as OnValidateModule callback
  • plugins.json extended with sha256 fields
  • Empty hash = no check (optional field)
  • Wrong hash on a required plugin = exception

Files

File Description
HashValidationApp.dpr Host application with TManifestValidator
Source/uMain.pas + .fmx MainForm -- displays loaded plugins
plugins.json Manifest with sha256 fields
HashValidationApp.dproj Delphi project (statically linked)
HashValidation.groupproj Build group (Framework + DemoPlugins + App)
update-hashes.ps1 Post-build script that auto-updates SHA256 hashes in plugins.json

How It Works

  1. Framework statically linked via DCC_UsePackage=rtl;fmx;FMXPluginFramework
  2. TManifestValidator.Create reads plugins.json including sha256 fields
  3. OnValidateModule := FManifest.ValidateModule -- hook is set
  4. For each BPL: compute SHA256, compare against manifest
  5. No hash in manifest = accept (backwards-compatible)
  6. Wrong hash + required=true = exception, app does not start

plugins.json Format

{
  "plugins": [
    {
      "name": "MyPlugin",
      "file": "MyPlugin",
      "required": true,
      "sha256": "a1b2c3d4e5f6..."
    }
  ]
}

The sha256 field is optional. An empty string or missing field means: no hash check for this plugin.

Generating Hashes

Via Delphi code:

uses Plugin.Manifest;
Writeln(TManifestValidator.ComputeHash('C:\Path\To\MyPlugin.bpl'));

Via command line (Windows):

certutil -hashfile MyPlugin.bpl SHA256

Try It Out

  1. Build the project via HashValidation.groupproj (Framework + Plugins + App)
  2. The post-build event automatically:
    • Copies plugins.json to the output directory
    • Runs update-hashes.ps1 to compute and insert SHA256 hashes for all BPLs
  3. Start the app -- all 3 plugins load successfully
  4. To test rejection, tamper with a hash in Bin\Win64_Release\plugins.json (e.g. change the first character):
    • With required: false -> plugin is skipped, app continues
    • With required: true -> exception, app does not start

The plugins.json in the source directory has empty sha256 fields. The post-build script fills them automatically in the output copy after each build.

Combinable

Hash check and Authenticode signature can be combined in the OnValidateModule callback:

function ValidateModule(const AFilePath: string): Boolean;
begin
  Result := FManifest.ValidateModule(AFilePath)
        and TSecurityValidator.VerifyAuthenticode(AFilePath);
end;

Limitations

  • Manifest is unsigned (for production: sign the manifest itself or embed it in a signed EXE)
  • Framework BPL is loaded by the OS before checks run (for full protection: use SecureBootstrap)
  • No downgrade protection without versioning in the manifest