This document explains how plugins are built, deployed, and packaged with CoseSignTool using the enhanced subdirectory architecture introduced in version 2.0.
The CoseSignTool plugin system includes automated build and deployment processes for both local development and CI/CD pipelines. The system supports:
- 🚀 Automatic Plugin Discovery: Zero-maintenance CI/CD packaging using naming conventions
- Enhanced Subdirectory Architecture: Each plugin gets its own subdirectory with isolated dependencies
- Legacy Flat Architecture: Backward compatibility with older plugin deployment methods
- Dependency Isolation: Plugins can use different versions of the same dependencies without conflicts
- Automated Deployment: MSBuild targets handle complex dependency copying automatically
The Microsoft's Signing Transparency (MST) plugin and Indirect Signature plugin are included as reference implementations and are automatically built and deployed with CoseSignTool releases.
The CI/CD pipeline automatically discovers and packages any project following this naming pattern:
<ProjectName>.Plugin.csproj
The GitHub Actions workflow uses this command to find all plugins:
# Discovers all plugin projects automatically
PLUGIN_PROJECTS=($(find . -name "*.Plugin.csproj" -type f | sed 's|^\./||' | sed 's|/[^/]*\.csproj$||'))CoseSignTool.MST.Plugin.csproj→ Automatically packagedCoseSignTool.IndirectSignature.Plugin.csproj→ Automatically packaged
To add a new plugin to automatic CI/CD packaging:
-
Name your project with
.Plugin.csprojsuffix:YourCompany.CustomSigning.Plugin.csproj AzureKeyVault.Integration.Plugin.csproj -
That's it! No other changes needed:
- ✅ Automatically discovered by CI/CD
- ✅ Automatically built with proper versioning
- ✅ Automatically deployed to release packages
- ✅ Automatically tested in CI pipeline
Projects not following the convention are ignored:
CoseSignTool.Utilities.csproj→ Not packaged (missing.Pluginsuffix)CustomTool.csproj→ Not packaged (missing.Pluginsuffix)
- Zero Maintenance: No manual updates to CI/CD scripts
- Fail-Safe: Cannot forget to include a plugin in deployment
- Scalable: Works with 2 plugins or 20 plugins
- Convention-Based: Clear, predictable naming rules
The GitHub Actions workflow handles plugin deployment in the release process:
-
Build Phase:
- Builds the entire solution including all plugins
- Tests all projects including plugin tests
- Publishes the main CoseSignTool application
-
Plugin Deployment Phase:
- Builds the CTS plugin with the same version as the main application
- Creates
pluginsdirectories in both debug and release outputs - Copies the plugin DLL and its dependencies
- Verifies plugin deployment and discovery
-
Verification Phase:
- Lists plugin directory contents
- Tests plugin command discovery in help output
- Ensures plugins are properly integrated
For local development, plugins are automatically built and deployed with the main application:
# Build CoseSignTool - plugins are automatically built and deployed (default)
dotnet build CoseSignTool
# To build WITHOUT plugins (faster for quick iterations)
dotnet build CoseSignTool -p:NoPlugins=true
# This automatically:
# - Discovers all *.Plugin.csproj projects
# - Builds each plugin project
# - Creates plugins/CoseSignTool.MST.Plugin/ subdirectory
# - Creates plugins/CoseSignTool.IndirectSignature.Plugin/ subdirectory
# - Copies each plugin and its dependencies to respective subdirectoriesWhat happens during plugin deployment:
- Plugin Discovery: All
*.Plugin.csprojprojects are automatically found - Build: Each plugin is built with the same Configuration and RuntimeIdentifier
- MST Plugin: Deployed to
plugins/CoseSignTool.MST.Plugin/with Azure dependencies - Indirect Signature Plugin: Deployed to
plugins/CoseSignTool.IndirectSignature.Plugin/with minimal dependencies - Dependency Isolation: Each plugin's dependencies are isolated in its subdirectory
After deployment with the enhanced subdirectory architecture, the directory structure looks like:
CoseSignTool/
├── bin/
│ └── [Debug|Release]/
│ └── net8.0/
│ ├── CoseSignTool.exe # Main application
│ ├── CoseSignTool.dll # Main library
│ ├── [other shared dependencies...]
│ └── plugins/ # Plugin directory
│ ├── CoseSignTool.MST.Plugin/ # MST plugin subdirectory
│ │ ├── CoseSignTool.MST.Plugin.dll
│ │ ├── CoseSignTool.MST.Plugin.deps.json
│ │ ├── Azure.Security.CodeTransparency.dll
│ │ ├── Azure.Core.dll
│ │ ├── Azure.Identity.dll
│ │ ├── Azure.Security.KeyVault.Keys.dll
│ │ ├── Microsoft.Bcl.AsyncInterfaces.dll
│ │ ├── Microsoft.Extensions.Configuration.CommandLine.dll
│ │ ├── Microsoft.Identity.Client.dll
│ │ ├── Newtonsoft.Json.dll
│ │ ├── System.ClientModel.dll
│ │ └── [other CTS-specific dependencies...]
│ ├── CoseSignTool.IndirectSignature.Plugin/ # Indirect Signature plugin subdirectory
│ │ ├── CoseSignTool.IndirectSignature.Plugin.dll
│ │ ├── CoseSignTool.IndirectSignature.Plugin.deps.json
│ │ └── [minimal plugin-specific dependencies...]
│ └── [legacy flat files for backward compatibility]
│ ├── CoseSignTool.MST.Plugin.dll
│ ├── CoseSignTool.IndirectSignature.Plugin.dll
│ └── [shared dependencies...]
Key Features:
- Dependency Isolation: Each plugin subdirectory contains all its required dependencies
- Version Independence: Plugins can use different versions of the same dependency
- Self-Contained: Each plugin directory is a complete, deployable unit
- Backward Compatibility: Legacy flat structure is maintained for older deployment methods
The enhanced plugin system provides sophisticated dependency management:
- Isolated Loading: Each plugin loads in its own
AssemblyLoadContext - Plugin-Specific Dependencies: Dependencies are resolved first from the plugin's subdirectory
- Shared Framework: Common .NET and Microsoft.Extensions assemblies are resolved from the main application
- Version Flexibility: Different plugins can use different versions of the same dependency
- Conflict Prevention: Dependencies in one plugin cannot interfere with another plugin
AssemblyLoadContext Resolution Order:
- Check if assembly is a shared framework assembly (System., Microsoft.Extensions., etc.)
- If yes: Delegate to default context (shared with main application)
- If no: Continue to plugin-specific resolution
- Look for assembly in plugin's subdirectory
- Use AssemblyDependencyResolver to find dependencies
- Fall back to default context if not found
For plugins deployed in the flat structure:
- Shared Dependencies: Libraries already included in the main application are not duplicated
- Plugin-Specific Dependencies: Libraries unique to plugins are copied to the plugins directory
- Conflict Risk: Multiple plugins using different versions of the same dependency may conflict
- Manual Management: Developers must carefully manage dependency versions
For optimal compatibility with the subdirectory architecture, configure your plugin project:
<PropertyGroup>
<!-- Ensure all dependencies are copied to output -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<PreserveCompilationContext>true</PreserveCompilationContext>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
</PropertyGroup>
<ItemGroup>
<!-- Explicitly mark plugin-specific dependencies for copying -->
<PackageReference Include="Azure.Core" Version="1.46.1">
<Private>true</Private>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</PackageReference>
</ItemGroup>To add a new plugin to the automated build process:
Ensure your plugin project:
- Targets .NET 8.0
- Has an assembly name ending with
.Plugin.dll - Implements
ICoseSignToolPlugin - Is included in the solution
Add your plugin to the CI/CD pipeline in .github/workflows/dotnet.yml:
# In the "Build and deploy CTS plugin" step
- name: Build and deploy plugins
run: |
# Build your plugin
dotnet build --configuration Debug --property:FileVersion=$VERSION YourPlugin/YourPlugin.csproj
dotnet build --configuration Release --property:FileVersion=$VERSION YourPlugin/YourPlugin.csproj
# Copy plugin DLL
cp YourPlugin/bin/Debug/net8.0/YourPlugin.Plugin.dll published/debug/plugins/
cp YourPlugin/bin/Release/net8.0/YourPlugin.Plugin.dll published/release/plugins/
# Copy plugin-specific dependencies
copy_if_exists "YourPlugin/bin/Debug/net8.0/YourSpecificDependency.dll" "published/debug/plugins/"
copy_if_exists "YourPlugin/bin/Release/net8.0/YourSpecificDependency.dll" "published/release/plugins/"If your plugin project follows the *.Plugin.csproj naming convention and is located anywhere under the CoseSignTool solution directory, it will be automatically discovered, built, and deployed.
The consolidated plugin discovery in CoseSignTool.csproj handles everything:
<!--Consolidated plugin discovery - reused by all plugin targets-->
<ItemGroup Condition="'$(DeployPlugins)' == 'true'">
<PluginProjects Include="$(MSBuildProjectDirectory)\..\**\*.Plugin.csproj" />
<PluginNames Include="@(PluginProjects->'%(Filename)')" />
</ItemGroup>No changes to CoseSignTool.csproj are required - just name your project correctly and it will be included automatically.
Add your plugin tests to the CI/CD pipeline:
# In the "Build and Test debug" step
dotnet test --no-restore YourPlugin.Tests/YourPlugin.Tests.csprojThe CI/CD pipeline includes automated verification:
- Directory Verification: Checks that plugins directory exists and contains expected files
- Discovery Testing: Runs CoseSignTool with
--helpto verify plugin commands appear - Integration Testing: Plugin tests ensure functionality works correctly
For manual verification:
# Build with plugins (default behavior)
dotnet build CoseSignTool
# Build without plugins (faster)
dotnet build CoseSignTool -p:NoPlugins=true
# Navigate to output directory
cd CoseSignTool/bin/Debug/net8.0
# Verify plugins directory exists
ls -la plugins/
# Test plugin discovery
./CoseSignTool --help
# Test specific plugin command
./CoseSignTool mst_register --helpPlugin not found:
- Verify assembly name ends with
.Plugin.dll - Check that plugin is in the
pluginsdirectory - Ensure plugin implements
ICoseSignToolPlugin
Missing dependencies:
- Check if plugin-specific dependencies are copied
- Verify dependency versions are compatible
- Look for dependency loading errors in console output
Plugin commands not appearing:
- Check plugin initialization doesn't throw exceptions
- Verify command names don't conflict with existing commands
- Review plugin discovery process in console output
Enable debug output to see plugin loading details:
# Set environment variable for detailed plugin loading
export COSESIGNTOOL_DEBUG_PLUGINS=true
./CoseSignTool --helpThe build process maintains plugin security:
- Authorized Directory: Plugins are only loaded from the
pluginssubdirectory - Signed Assemblies: Plugin assemblies use the same strong name signing as the main application
- Dependency Isolation: Plugin dependencies are isolated in the plugins directory
The CI/CD pipeline includes security measures:
- Version Consistency: All components built with the same version number
- Dependency Validation: Only known, necessary dependencies are copied
- Verification Steps: Automated checks ensure deployment integrity
The plugin build process is optimized for:
- Parallel Builds: Plugins can be built in parallel with the main application
- Incremental Builds: Only changed plugins are rebuilt in local development
- Selective Copying: Only necessary dependencies are copied to reduce package size
Plugin deployment affects runtime:
- Load Time: Plugins are loaded at startup (minimal impact)
- Memory Usage: Plugin assemblies are loaded into memory when discovered
- Startup Time: Plugin discovery adds small overhead to application startup
- Dependency Updates: Keep plugin dependencies up to date with main application
- Version Alignment: Ensure plugin versions align with main application releases
- Test Coverage: Maintain test coverage for plugin build and deployment process
Monitor the following in CI/CD:
- Build Times: Track if plugin builds are impacting overall build performance
- Package Sizes: Monitor if plugin dependencies are significantly increasing package size
- Test Results: Ensure plugin tests continue to pass in all environments
For more information about developing plugins, see:
- Plugins.md - Complete plugin development guide
- PluginQuickStart.md - Quick start guide for developers
- PluginAPI.md - Complete API reference