Optimus.Bank.Extensions.DownstreamEncryption
1.0.6
dotnet add package Optimus.Bank.Extensions.DownstreamEncryption --version 1.0.6
NuGet\Install-Package Optimus.Bank.Extensions.DownstreamEncryption -Version 1.0.6
<PackageReference Include="Optimus.Bank.Extensions.DownstreamEncryption" Version="1.0.6" />
<PackageVersion Include="Optimus.Bank.Extensions.DownstreamEncryption" Version="1.0.6" />
<PackageReference Include="Optimus.Bank.Extensions.DownstreamEncryption" />
paket add Optimus.Bank.Extensions.DownstreamEncryption --version 1.0.6
#r "nuget: Optimus.Bank.Extensions.DownstreamEncryption, 1.0.6"
#:package Optimus.Bank.Extensions.DownstreamEncryption@1.0.6
#addin nuget:?package=Optimus.Bank.Extensions.DownstreamEncryption&version=1.0.6
#tool nuget:?package=Optimus.Bank.Extensions.DownstreamEncryption&version=1.0.6
Optimus.Bank.Extensions.DownstreamEncryption - Package Design & Integration Guide
This document covers the reusable NuGet package design for
Optimus.Bank.Extensions.DownstreamEncryptionand how consuming projects integrate with it. Use it as a reference and architectural guide, not a verbatim copy - your service-specific config shapes, helper classes, and endpoint structures will differ per project.
Overview
The encryption infrastructure described in the OptiNotify Downstream Encryption Design Guide has been extracted into a reusable internal NuGet package. Consuming projects get all the cryptographic plumbing for free and only need to provide their own service-specific config classes and API helper classes.
The package is exclusively concerned with PGP encryption, decryption, and secret management. HTTP client implementation, authentication strategies, and API communication are entirely the consuming project's responsibility.
Prerequisites
Every consuming project must:
- Reference
OptimusCryptic.dll- this is a required local DLL dependency. Add it via a<Reference>in your.csproj:
<ItemGroup>
<Reference Include="OptimusCryptic">
<HintPath>..\libs\OptimusCryptic.dll</HintPath>
</Reference>
</ItemGroup>
- Reference the package from the internal NuGet feed:
<ItemGroup>
<PackageReference Include="Optimus.Bank.Extensions.DownstreamEncryption" Version="1.0.1" />
</ItemGroup>
- Add the appropriate Vault Options - see the Vault NuGet package for more info.
Registration API
Optimus.Bank.Extensions.DownstreamEncryption exposes AddDownstreamEncryption(). Call this once in your Program.cs or Startup.cs, passing in IConfiguration:
builder.Services.AddDownstreamEncryption(builder.Configuration);
This registers the encryption infrastructure only. HTTP clients, authentication, and any other API communication concerns are registered separately by the consuming project.
How Consuming Projects Use the Package
The package handles all encryption and secret management. A consuming project needs to provide three things per downstream service:
1. A config class
The only property required from the package is Credentials. Any additional properties - base URLs, endpoints, headers - are entirely up to the consuming project and are not enforced by the package.
// YourProject/Config/PaymentApiConfigs.cs
public class PaymentApiConfigs
{
[ValidateObjectMembers, Required] public InternalApiCredentials Credentials { get; set; } // from package
// Everything below is optional and up to the consuming project
public string BaseURL { get; set; } = string.Empty;
public PaymentApiEndpointsConfig Endpoints { get; set; } = new();
}
public class PaymentApiEndpointsConfig
{
public string InitiatePayment { get; set; } = string.Empty;
public string GetPaymentStatus { get; set; } = string.Empty;
}
InternalApiCredentials, EncryptedInternalApiPgpConfigs, and KeyVaultOptions all come from the package - no need to redefine them.
2. An appsettings.json block
"PaymentApiConfigs": {
"BaseURL": "https://your-host/api/v1",
"Credentials": {
"PgpConfigs": {
"EncryptedClientId": "<OptimusCryptic.aesEncrypt output>",
"EncryptedApiPublicKeyRelativePath": "<OptimusCryptic.aesEncrypt output>",
"EncryptedServerPrivateKeyPath": "<OptimusCryptic.aesEncrypt output>",
"EncryptedServerPassphrase": "<OptimusCryptic.aesEncrypt output>",
"ApiChannelIdHeader": "ClientKey"
},
"KeyVaultOptions": {
"ServiceName": "payment_<your-service-guid>"
},
"UseKeyVault": false,
"EncryptRequest": true
},
"Endpoints": {
"InitiatePayment": "/Payment/Initiate",
"GetPaymentStatus": "/Payment/Status/{reference}"
}
}
3. A helper class
The consuming helper takes IEncryptedInternalApiHelperFactory from DI and passes only the credentials into Create(). The factory resolves the correct secret provider internally - ISecretProviderFactory and the concrete providers never appear in consumer code.
HTTP execution is handled by whatever client the consuming project uses - HttpClient, RestSharp, or any other implementation.
// YourProject/Helpers/PaymentApiHelper.cs
public class PaymentApiHelper : IPaymentApiHelper
{
private readonly ILogger<PaymentApiHelper> _logger;
private readonly PaymentApiConfigs _config;
private readonly IEncryptedInternalApiHelper _crypto;
public PaymentApiHelper(
IEncryptedInternalApiHelperFactory cryptoFactory,
ILogger<PaymentApiHelper> logger,
IOptionsSnapshot<PaymentApiConfigs> config)
{
_logger = logger;
_config = config.Value;
// Pass credentials only - factory handles provider resolution internally
_crypto = cryptoFactory.Create(config.Value.Credentials);
}
public async Task<ApiResponse<PaymentResponse?>> InitiatePayment(PaymentRequest request, CancellationToken token)
{
// Encrypt outgoing body
var encryptedPayload = await _crypto.EncryptInternalRequests(request, token);
var body = new { Data = encryptedPayload };
// HTTP execution is the consuming project's responsibility
// Use HttpClient, RestSharp, or any other implementation here
var rawResponse = await _yourHttpClient.PostAsync(...);
if (string.IsNullOrWhiteSpace(rawResponse?.Data))
return ApiResponse<PaymentResponse?>.CreateFailure(ResponseCodeInfo.FailedRequest);
// Decrypt incoming response
var decrypted = await _crypto.DecryptInternalResponse(rawResponse.Data, token);
if (string.IsNullOrWhiteSpace(decrypted))
return ApiResponse<PaymentResponse?>.CreateFailure(ResponseCodeInfo.FailedRequest);
return JsonConvert.DeserializeObject<ApiResponse<PaymentResponse?>>(decrypted)
?? ApiResponse<PaymentResponse?>.CreateFailure(ResponseCodeInfo.FailedRequest);
}
}
The package's surface area in consuming code is limited to IEncryptedInternalApiHelperFactory, IEncryptedInternalApiHelper, InternalApiCredentials, and the config model types. Everything else is the consuming project's concern.
Full DI Registration Example (Consuming Project)
// Program.cs
// 1. Register package encryption infrastructure
builder.Services.AddDownstreamEncryption(builder.Configuration);
// 2. Bind your service-specific config
builder.Services.AddOptions<PaymentApiConfigs>()
.BindConfiguration("PaymentApiConfigs")
.ValidateDataAnnotations()
.ValidateOnStart();
// 3. Register your helper
builder.Services.AddScoped<IPaymentApiHelper, PaymentApiHelper>();
// 4. Register your own HTTP client and auth - not the package's concern
// e.g. builder.Services.AddHttpClient<IPaymentApiHelper, PaymentApiHelper>();
IEncryptedInternalApiHelperis not registered in DI. It is produced exclusively byIEncryptedInternalApiHelperFactory.Create()and held as a field by the consuming helper. Registering it in DI would imply a single shared instance, which would break per-service credential isolation.
Switching Key Providers at Runtime
No code changes are needed. The provider is determined entirely by the UseKeyVault flag in appsettings.json for each service independently:
// Service A - file-based keys (dev/staging)
"KeyVaultOptions": { "UseKeyVault": false,"EncryptRequest": "true", "ServiceName": "account_..." }
// Service B - Key Vault (production)
"KeyVaultOptions": { "UseKeyVault": true, "EncryptRequest": "true", "ServiceName": "payment_..." }
// Service C - None (Unencrypted option)
"KeyVaultOptions": { "UseKeyVault": true, "EncryptRequest": "false", "ServiceName": "payment_..." }
You can have two services in the same project using different providers simultaneously. IEncryptedInternalApiHelperFactory.Create(credentials) resolves the correct provider per instance at construction time based on the credentials passed in.
Adding a New Downstream Service (Checklist)
For each new encrypted downstream API a project needs to integrate with:
- Add a config class with
InternalApiCredentialsas the only required property from the package - Add the
appsettings.jsonblock withOptimusCryptic-encrypted values underCredentials - Add an
AddOptions<YourConfig>()binding inProgram.cs - Create a helper class that takes
IEncryptedInternalApiHelperFactoryand callsCreate(config.Value.Credentials) - Wire up your own HTTP client implementation in the helper
- Register your helper in DI
- Place PGP key files in the configured relative path (file mode) or register secrets in Key Vault (KV mode)
No changes to the package are needed.
Handling Encrypted vs Unencrypted Requests
A new configuration property, EncryptRequest, has been introduced within the credentials. This boolean flag determines whether a request should be encrypted before being sent.
The IEncryptedInternalApiHelper interface provides a method that exposes the value of this property, allowing consumers to dynamically decide how to process outgoing requests.
Implementation Guidance
Before sending a request, you should:
- Check the value of
EncryptRequestvia the helper. - Based on the result:
- If
true: Proceed with encrypting the request payload. - If
false: Send the request without encryption.
- If
This approach ensures flexibility and allows the system to support both encrypted and unencrypted request flows seamlessly, depending on the configured credentials.
Example (Pseudo-Logic)
if (_crypto.UseEncryption)
{
// Encrypt and send request
}
else
{
// Send request as-is without encryption and return a response
}
Package Versioning & Ownership
| Concern | Recommendation |
|---|---|
| Breaking interface changes | Increment major version (2.0.0) |
| New providers (e.g. HashiCorp Vault) | New package Optimus.Bank.Extensions.DownstreamEncryption.Vault following the same pattern |
OptimusCryptic updates |
Consuming projects update their local DLL reference independently; no package change needed unless the call signature changes |
IVaultService updates |
Update the vault package reference and re-publish |
Security Notes
- All
Encrypted*config values are produced byOptimusCryptic.aesEncryptand decrypted at runtime byOptimusCryptic.aesDecrypt. The AES key underpinningOptimusCrypticmust be protected separately (environment variable or secrets manager) - it is the root secret for all config-level encryption. SecretLoaderreads from disk on every call and caches.KeyVaultSecretProvidercaches inIMemoryCacheonly - never Redis or any distributed store.- Cache keys in
KeyVaultSecretProviderare namespaced byserviceName, so multiple services in the same process remain isolated. IEncryptedInternalApiHelperis not registered in DI intentionally - see the note in the DI Registration section above.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Caching.Memory (>= 10.0.5)
- Microsoft.Extensions.Options (>= 10.0.5)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.5)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.5)
- optimus.bank.library.PGP (>= 0.1.2)
- Optimus.Bank.Security.Vault (>= 1.1.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|