Offline Verification Crypto Provider - Security Guide
Document Version: 1.1 Last Updated: 2026-05-30 Status: Active (verification-only; signing path is a non-functional stub — see warning below) Audience: Security Engineers, Platform Operators, DevOps Teams Sprint: SPRINT_1000_0007_0002
CRITICAL — read before relying on this provider. As implemented today (
src/__Libraries/StellaOps.Cryptography.Plugin.OfflineVerification/OfflineVerificationCryptoProvider.cs), this provider is a verification-only component. Its real, tested capability isCreateEphemeralVerifier(...)(signature verification from a raw public key).
- Signing does not work.
GetSigner(...)returnsEcdsaSigner/RsaSignerwhoseSignAsync/VerifyAsyncgenerate a fresh ephemeral key on every call (ECDsa.Create(curve)/RSA.Create()) and never load key material from the suppliedCryptoKeyReference. A signature produced this way cannot be verified by anyone, and the in-code comments say so (“In a real implementation, would load key material”). Do not use the signing path for any real signature.- Key management is not supported.
UpsertSigningKeyandRemoveSigningKeythrowNotSupportedException;GetSigningKeysreturns an empty collection.ExportPublicJsonWebKey()throwsNotSupportedExceptionon every signer/verifier this provider returns.The signing and key-management examples in this guide are therefore marked NOT IMPLEMENTED where they appear, and describe the intended (roadmap) shape, not current behavior.
Table of Contents
- Overview
- Architecture
- Security Model
- Algorithm Support
- Deployment Scenarios
- API Reference
- Trust Establishment
- Threat Model
- Compliance
- Best Practices
- Troubleshooting
Overview
The OfflineVerificationCryptoProvider is a cryptographic abstraction layer that wraps .NET BCL (System.Security.Cryptography) to enable configuration-driven cryptography in offline, air-gapped, and sovereignty-constrained environments.
Purpose
- Offline Operations: Function without network access to external cryptographic services
- Deterministic Hashing: Reproducible SHA-2 digests for compliance auditing (signatures are not deterministic — there is no RFC 6979 support; and this provider does not sign at all)
- Zero External Dependencies: No cloud KMS, HSMs, or online certificate authorities required
- Regional Neutrality: NIST-approved algorithms without regional compliance constraints
Key Features
- Ephemeral verification for public-key-only scenarios (DSSE, JWT, JWS) over ECDSA (ES256/384/512) and RSA (RS256/384/512, PS256/384/512) — this is the provider’s primary, fully implemented capability (
CreateEphemeralVerifier). - SHA-2 family content hashing (SHA-256/384/512), with
SHA256/SHA-256aliasing. - Algorithm capability matrix exposed via
Supports(CryptoCapability, algorithmId). - Configuration-driven plugin selection via
etc/crypto-plugins-manifest.json(provider idoffline-verification, priority45,enabledByDefault: true). - Thin wrapper over .NET BCL (
System.Security.Cryptography) primitives.
NOT IMPLEMENTED: ECDSA/RSA signing with managed keys.
GetSigner(...)exists but its signers do not load key material (see the warning at the top of this document). Treat this provider as verification-only.
Architecture
Component Hierarchy
┌─────────────────────────────────────────────────────────┐
│ Production Code (AirGap, Scanner, Attestor) │
│ ├── Uses: ICryptoProvider abstraction │
│ └── Never touches: System.Security.Cryptography │
└─────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ StellaOps.Cryptography (Core Abstraction) │
│ ├── ICryptoProvider interface │
│ ├── ICryptoSigner interface │
│ ├── ICryptoHasher interface │
│ └── CryptoProviderRegistry │
└─────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ OfflineVerificationCryptoProvider (Plugin) │
│ ├── BclHasher (SHA-256/384/512) │
│ ├── EcdsaSigner (ES256/384/512) │
│ ├── RsaSigner (RS/PS 256/384/512) │
│ ├── EcdsaEphemeralVerifier (public-key-only) │
│ └── RsaEphemeralVerifier (public-key-only) │
└─────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ System.Security.Cryptography (.NET BCL) │
│ ├── ECDsa (NIST P-256/384/521) │
│ ├── RSA (2048/3072/4096-bit) │
│ └── SHA256/SHA384/SHA512 │
└─────────────────────────────────────────────────────────┘
Isolation Boundaries
Crypto Operations Allowed:
- ✅ Inside
StellaOps.Cryptography.Plugin.*projects - ✅ Inside unit test projects (
__Tests/**) - ❌ NEVER in production application code
Enforcement Mechanisms (intended design — see status notes):
- Static Analysis:
scripts/audit-crypto-usage.ps1— NOT IMPLEMENTED. This script does not exist in the repository. The only present audit script istools/scripts/audit-reconciliation.ps1(unrelated). The boundary is currently a convention, not an automated gate. - CI Validation:
.gitea/workflows/crypto-compliance.yml— NOT ACTIVE. The workflow file exists only under.gitea/workflows-archived/crypto-compliance.ymland references the missing audit script above, so it cannot run as written. - Code Review: Manual review on pull requests (no automated crypto-usage check is wired today).
Security Model
Threat Categories
| Threat | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Key Extraction | Medium | High | In-memory keys only, minimize key lifetime |
| Side-Channel (Timing) | Low | Medium | .NET BCL uses constant-time primitives |
| Algorithm Downgrade | Very Low | Critical | Runtime algorithm allowlist (Supports/GetHasher/CreateEphemeralVerifier switch throws NotSupportedException for unlisted IDs) |
| Public Key Substitution | Medium | Critical | Fingerprint verification, out-of-band trust (must be done by the caller; this provider does NOT validate keys against a trust store) |
| Replay Attack | Medium | Medium | Include timestamps in signed payloads (caller responsibility) |
| Man-in-the-Middle | Low (offline) | N/A | Physical media transport |
Note on the allowlist: algorithm gating is a runtime
switchin the provider, not a compile-time check. Unsupported algorithm IDs throwNotSupportedExceptionwhen a hasher, signer, or ephemeral verifier is requested.
Trust Boundaries
┌────────────────────────────────────────────────────────┐
│ Trusted Computing Base (TCB) │
│ ├── .NET Runtime (Microsoft-signed) │
│ ├── OfflineVerificationCryptoProvider (BUSL-1.1) │
│ └── Pre-distributed Public Key Fingerprints │
└────────────────────────────────────────────────────────┘
▲
│ Trust Anchor
│
┌────────────────────────────────────────────────────────┐
│ Untrusted Zone │
│ ├── Container Images (to be verified) │
│ ├── SBOMs (to be verified) │
│ └── VEX Documents (to be verified) │
└────────────────────────────────────────────────────────┘
Trust Establishment:
- Pre-distribution: Public key fingerprints embedded in airgap bundle
- Out-of-Band Verification: Manual verification via secure channel
- Chain of Trust: Each signature verified against trusted fingerprints
Algorithm Support
Verification (and signing algorithm IDs)
The algorithm IDs below are what Supports(...) reports and what CreateEphemeralVerifier(...) accepts. Verification (CreateEphemeralVerifier) is implemented for all nine IDs. The signing path (GetSigner) accepts the same IDs but is a non-functional stub (see the warning at the top of this document), so the “Use Case” column describes the intended role.
| Algorithm | Curve/Key Size | Hash | Padding | Ephemeral verify | Use Case (intended) |
|---|---|---|---|---|---|
| ES256 | NIST P-256 | SHA-256 | N/A | ✅ | DSSE envelopes, in-toto attestations |
| ES384 | NIST P-384 | SHA-384 | N/A | ✅ | High-security SBOM signatures |
| ES512 | NIST P-521 | SHA-512 | N/A | ✅ | Long-term archival signatures |
| RS256 | 2048+ bits | SHA-256 | PKCS1 | ✅ | Legacy compatibility |
| RS384 | 2048+ bits | SHA-384 | PKCS1 | ✅ | Legacy compatibility |
| RS512 | 2048+ bits | SHA-512 | PKCS1 | ✅ | Legacy compatibility |
| PS256 | 2048+ bits | SHA-256 | PSS | ✅ | Recommended RSA |
| PS384 | 2048+ bits | SHA-384 | PSS | ✅ | Recommended RSA |
| PS512 | 2048+ bits | SHA-512 | PSS | ✅ | Recommended RSA |
Manifest gap (flagged):
etc/crypto-plugins-manifest.jsonlistsverification:capabilities only for ES256/384/512 and RS256/384/512 — it omitsverification:PS256/384/512even though the code’sSupports(CryptoCapability.Verification, ...)returnstruefor PS algorithms andCreateEphemeralVerifierhandles them. The plugin’s own README declares the PS verification capabilities; the manifest does not. Treat PS verification as supported (code is source of truth), and note the manifest needs the threeverification:PS*lines added.ES256/384/512 map to NIST P-256/P-384/P-521 respectively (note: ES512 uses curve P-521, not P-512).
Content Hashing
| Algorithm | Output Size | Performance | Use Case |
|---|---|---|---|
| SHA-256 | 256 bits | Fast | Default for most use cases |
| SHA-384 | 384 bits | Medium | Medium-security requirements |
| SHA-512 | 512 bits | Medium | High-security requirements |
Normalization: Both SHA-256 and SHA256 formats accepted, normalized to SHA-256.
Password Hashing
Not functional. GetPasswordHasher(...) always throws NotSupportedException (“Password hashing is not supported by the offline verification provider.”).
Source inconsistency (flagged):
Supports(CryptoCapability.PasswordHashing, "PBKDF2")andSupports(..., "Argon2id")both returntrue, which contradictsGetPasswordHasheralways throwing. A caller that branches onSupports(...)before callingGetPasswordHasher(...)will hit an unexpected exception. TheSupportsswitch forPasswordHashingshould returnfalsefor this provider, orGetPasswordHashershould be implemented. Until reconciled, treat password hashing as unsupported.
Use dedicated password hashers instead:
Argon2idPasswordHasherfor modern password hashingPbkdf2PasswordHasherfor legacy compatibility
Deployment Scenarios
Scenario 1: Air-Gapped Container Scanning
Environment: Offline network segment, no internet access
Configuration:
{
"cryptoProvider": "offline-verification",
"algorithms": {
"signing": "ES256",
"hashing": "SHA-256"
},
"trustRoots": {
"fingerprints": [
"sha256:a1b2c3d4e5f6....",
"sha256:f6e5d4c3b2a1...."
]
}
}
Trust Establishment:
- Pre-distribute trust bundle via USB/DVD:
offline-kit.tar.gz - Bundle contains:
- Public key fingerprints (
trust-anchors.json) - Root CA certificates (if applicable)
- Offline crypto provider plugin
- Public key fingerprints (
- Operator verifies bundle signature using out-of-band channel
Workflow:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Scan │──▶│ Generate │──▶│ Sign │──▶│ Verify │
│ Container│ │ SBOM │ │ with ES256│ │ Signature│
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │
▼ ▼
OfflineVerificationCryptoProvider
Scenario 2: Sovereign Cloud Deployment
Environment: National cloud with data residency requirements
Configuration:
{
"cryptoProvider": "offline-verification",
"jurisdiction": "world",
"compliance": ["NIST", "offline-airgap"],
"keyRotation": {
"enabled": true,
"intervalDays": 90
}
}
Key Considerations:
- Keys generated and stored within sovereign boundary
- No external KMS dependencies
- Audit trail for all cryptographic operations
- Compliance with local data protection laws
Scenario 3: CI/CD Pipeline — Hashing and Verification
Environment: Build server
NOT IMPLEMENTED — deterministic signing. This provider performs no signing (the signing path is a non-functional stub) and has no RFC 6979 / deterministic-ECDSA support. There is no BouncyCastle dependency: the project references only
StellaOps.CryptographyandMicrosoft.IdentityModel.Tokens(seeStellaOps.Cryptography.Plugin.OfflineVerification.csproj). Standard .NETECDsa.SignDatais non-deterministic. ThedeterministicSigningconfig key below is not honored by any code in this provider.
What this provider can actually contribute in CI/CD:
- Content hashing of artifacts via
GetHasher("SHA-256")(deterministic, known-answer tested). - Signature verification of artifacts signed elsewhere, via
CreateEphemeralVerifier(...)given the signer’s public key in SPKI/DER form.
For actual signing in CI/CD, use a key-managing provider (e.g., the default/FIPS/eIDAS providers in etc/crypto-plugins-manifest.json) or an HSM/KMS-backed provider — not this one.
Illustrative (NOT honored) configuration:
{
"cryptoProvider": "offline-verification",
"deterministicSigning": true,
"algorithms": {
"signing": "ES256",
"hashing": "SHA-256"
}
}
API Reference
ICryptoProvider.CreateEphemeralVerifier
CreateEphemeralVerifier is a default method on the ICryptoProvider interface (the base implementation throws NotSupportedException); the offline provider overrides it with a working implementation. It is the only fully functional asymmetric operation this provider offers.
Signature:
ICryptoSigner CreateEphemeralVerifier(
string algorithmId,
ReadOnlySpan<byte> publicKeyBytes)
Purpose: Create a verification-only signer from raw public key bytes, without key persistence or management overhead.
Parameters:
algorithmId: Algorithm identifier — one ofES256/384/512,RS256/384/512,PS256/384/512publicKeyBytes: Public key in SubjectPublicKeyInfo (SPKI) format, DER-encoded
Returns: ICryptoSigner (an EcdsaEphemeralVerifier or RsaEphemeralVerifier) with:
VerifyAsync(data, signature)- Returnstrueif signature validSignAsync(data)- ThrowsNotSupportedException(“Ephemeral verifier does not support signing operations.”)ExportPublicJsonWebKey()- ThrowsNotSupportedException(“JWK export not supported for ephemeral verifiers.”)KeyId- Returns"ephemeral"AlgorithmId- Returns the specified algorithm
Throws:
NotSupportedException— at construction, only whenalgorithmIdis not one of the nine supported IDs (message includes the offending ID).CryptographicException— deferred toVerifyAsync, not thrown byCreateEphemeralVerifier. The public key is parsed lazily viaImportSubjectPublicKeyInfoinsideVerifyAsync; a malformed SPKI key surfaces there. (CreateEphemeralVerifieritself only copies the bytes.)
Usage Example:
// DSSE envelope verification
var envelope = DsseEnvelope.Parse(envelopeJson);
var trustRoots = LoadTrustRoots();
foreach (var signature in envelope.Signatures)
{
// Get public key from trust store
if (!trustRoots.PublicKeys.TryGetValue(signature.KeyId, out var publicKeyBytes))
continue;
// Verify fingerprint
var fingerprint = ComputeFingerprint(publicKeyBytes);
if (!trustRoots.TrustedFingerprints.Contains(fingerprint))
continue;
// Create ephemeral verifier
var verifier = cryptoProvider.CreateEphemeralVerifier("PS256", publicKeyBytes);
// Build pre-authentication encoding (PAE)
var pae = BuildPAE(envelope.PayloadType, envelope.Payload);
// Verify signature
var isValid = await verifier.VerifyAsync(pae, Convert.FromBase64String(signature.Signature));
if (isValid)
return ValidationResult.Success();
}
return ValidationResult.Failure("No valid signature found");
ICryptoHasher.ComputeHash
Signature:
byte[] ComputeHash(ReadOnlySpan<byte> data)
Usage Example:
var hasher = cryptoProvider.GetHasher("SHA-256");
var hash = hasher.ComputeHash(fileBytes);
var hex = Convert.ToHexString(hash).ToLowerInvariant();
ICryptoSigner.SignAsync / VerifyAsync (NOT IMPLEMENTED for managed-key signing)
Signatures:
ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken ct = default)
ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken ct = default)
JsonWebKey ExportPublicJsonWebKey() // always throws NotSupportedException in this provider
NOT IMPLEMENTED — the example below does not work with this provider. It is retained to document the intended shape only. With
OfflineVerificationCryptoProvider:
UpsertSigningKey(...)throwsNotSupportedException(“The offline verification provider does not support key management.”), so the example fails on the first line that uses it.- Even calling
GetSigner(...)directly and thenSignAsync/VerifyAsyncdoes not round-trip: each call instantiates a brand-new ephemeral key (ECDsa.Create(curve)/RSA.Create()) and ignores theCryptoKeyReference. A signature produced bySignAsyncwill not verify, andVerifyAsynconEcdsaSigner/RsaSignerchecks against an unrelated random key.For real verification use
CreateEphemeralVerifier(...)(above). For real signing use a key-managing crypto provider.
Intended usage (does NOT work here):
// Signing — UpsertSigningKey THROWS on this provider; round-trip would fail even via GetSigner
var signingKey = new CryptoSigningKey(
reference: new CryptoKeyReference("my-key"),
algorithmId: "ES256",
privateParameters: ecParameters,
createdAt: DateTimeOffset.UtcNow);
cryptoProvider.UpsertSigningKey(signingKey); // throws NotSupportedException
var signer = cryptoProvider.GetSigner("ES256", new CryptoKeyReference("my-key"));
var signature = await signer.SignAsync(data);
// Verification
var isValid = await signer.VerifyAsync(data, signature);
Trust Establishment
Offline Trust Bundle Structure
offline-kit.tar.gz
├── trust-anchors.json # Public key fingerprints
├── public-keys/ # Public keys in SPKI format
│ ├── scanner-key-001.pub
│ ├── scanner-key-002.pub
│ └── attestor-key-001.pub
├── metadata/
│ ├── bundle-manifest.json # Bundle metadata
│ └── bundle-signature.sig # Bundle self-signature
└── crypto-plugins/
└── StellaOps.Cryptography.Plugin.OfflineVerification.dll
trust-anchors.json Format
{
"version": "1.0",
"createdAt": "2025-12-23T00:00:00Z",
"expiresAt": "2026-12-23T00:00:00Z",
"trustAnchors": [
{
"keyId": "scanner-key-001",
"algorithmId": "ES256",
"fingerprint": "sha256:a1b2c3d4e5f6...",
"purpose": "container-scanning",
"notBefore": "2025-01-01T00:00:00Z",
"notAfter": "2026-01-01T00:00:00Z"
}
],
"bundleSignature": {
"keyId": "bundle-signing-key",
"algorithmId": "ES256",
"signature": "base64encodedSignature=="
}
}
Fingerprint Computation
private string ComputeFingerprint(byte[] publicKeyBytes)
{
var hasher = cryptoProvider.GetHasher("SHA-256");
var hash = hasher.ComputeHash(publicKeyBytes);
return "sha256:" + Convert.ToHexString(hash).ToLowerInvariant();
}
Out-of-Band Verification Process
- Bundle Reception: Operator receives
offline-kit.tar.gzvia physical media - Checksum Verification: Compare SHA-256 hash against value published via secure channel
sha256sum offline-kit.tar.gz # Compare with published value: a1b2c3d4e5f6... - Bundle Signature Verification: Extract bundle, verify self-signature using bootstrap public key
- Trust Anchor Review: Manual review of trust-anchors.json entries
- Deployment: Extract crypto plugin and trust anchors to deployment directory
Threat Model
Attack Surface Analysis
| Attack Vector | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Memory Dump | Medium | High | Use ephemeral keys, minimize key lifetime |
| Side-Channel (Timing) | Low | Medium | .NET BCL uses constant-time primitives |
| Algorithm Substitution | Very Low | Critical | Runtime algorithm allowlist (switch throws NotSupportedException for unlisted IDs) |
| Public Key Substitution | Medium | Critical | Fingerprint verification, out-of-band trust (caller-side; provider does not validate keys) |
| Replay Attack | Medium | Medium | Include timestamps in signed payloads (caller-side) |
| Man-in-the-Middle | Low (offline) | N/A | Physical media transport |
Mitigations by Threat
T1: Private Key Extraction
- Control: This provider holds no managed private keys — it does not implement key management (
UpsertSigningKey/RemoveSigningKeythrow;GetSigningKeysis empty). The only key material it touches is the caller-supplied public key passed toCreateEphemeralVerifier, which is non-secret. Private-key custody, rotation, and revocation belong to the signing provider (not this one). - Monitoring: Key-usage logging is a caller/host concern; this provider does not log.
- Response: Handled by whichever provider owns the signing keys.
T2: Public Key Substitution
- Control: SHA-256 fingerprint verification before use
- Monitoring: Alert on fingerprint mismatches
- Response: Investigate trust bundle integrity
T3: Signature Replay
- Control: Include timestamp and nonce in signed payloads
- Monitoring: Detect signatures older than TTL
- Response: Reject replayed signatures
T4: Algorithm Downgrade
- Control: Hardcoded runtime algorithm allowlist in the provider’s
Supports/GetHasher/GetSigner/CreateEphemeralVerifierswitches (no SHA-1, no MD5, no non-NIST curves) - Monitoring: Algorithm selection is not logged by the provider (host concern)
- Response: Unsupported algorithm IDs throw
NotSupportedExceptionat call time
Compliance
NIST Standards
| Standard | Requirement | Compliance |
|---|---|---|
| FIPS 186-4 | Digital Signature Standard | ✅ ECDSA with P-256/384/521, RSA-PSS |
| FIPS 180-4 | Secure Hash Standard | ✅ SHA-256/384/512 |
| FIPS 140-2 | Cryptographic Module Validation | ⚠️ .NET BCL (software-only, not validated) |
Notes:
- For FIPS 140-2 Level 3+ compliance, use HSM-backed crypto provider
- Software-only crypto acceptable for FIPS 140-2 Level 1
RFC Standards
| RFC | Title | Compliance |
|---|---|---|
| RFC 8017 | PKCS #1: RSA Cryptography v2.2 | ✅ RSASSA-PKCS1-v1_5, RSASSA-PSS (verification) |
| RFC 6979 | Deterministic DSA/ECDSA | ❌ NOT IMPLEMENTED. No BouncyCastle dependency exists (csproj references only StellaOps.Cryptography and Microsoft.IdentityModel.Tokens); .NET ECDsa signing is non-deterministic. |
| RFC 5280 | X.509 Public Key Infrastructure | ✅ Consumes SubjectPublicKeyInfo (SPKI) DER public keys |
| RFC 7515 | JSON Web Signature (JWS) | ⚠️ Algorithm IDs match JWS (ES/RS/PS 256/384/512) and are verifiable here; this provider does not produce JWS signatures. ExportPublicJsonWebKey() throws. |
Regional Standards
| Region | Standard | Compliance |
|---|---|---|
| European Union | eIDAS Regulation (EU) 910/2014 | ❌ Use eIDAS plugin |
| Russia | GOST R 34.10-2012 | ❌ Use CryptoPro plugin |
| China | SM2/SM3/SM4 (GM/T 0003-2012) | ❌ Use SM crypto plugin |
Best Practices
Key Management
This provider does not manage keys. The DO/DON’T list below is general guidance for the signing provider you pair with it; for
OfflineVerificationCryptoProvideritself the only relevant item is “use ephemeral verifiers for public-key-only scenarios.”
✅ DO:
- Rotate signing keys every 90 days (in the signing provider)
- Use separate keys for different purposes
- Store private keys in memory only / in an HSM (in the signing provider)
- Use ephemeral verifiers for public-key-only scenarios (this provider)
- Audit all key usage events (host/signing provider)
❌ DON’T:
- Reuse keys across environments
- Store keys in configuration files
- Use RSA keys smaller than 2048 bits
- Use SHA-1 or MD5
- Bypass fingerprint verification
Algorithm Selection
Recommended:
- ES256 (ECDSA P-256/SHA-256) - Best balance
- PS256 (RSA-PSS 2048-bit/SHA-256) - For RSA-required scenarios
- SHA-256 - Default hashing algorithm
Avoid:
- ES512 / PS512 - Performance overhead
- RS256 / RS384 / RS512 - Legacy PKCS1 padding
Performance Optimization
Caching:
// Cache hashers (thread-safe, reusable)
private readonly ICryptoHasher _sha256Hasher;
public MyService(ICryptoProviderRegistry registry)
{
_sha256Hasher = registry.ResolveHasher("SHA-256").Hasher;
}
Troubleshooting
Common Issues
Issue: NotSupportedException: Algorithm 'RS256' is not supported
Resolution:
- Verify algorithm ID is exactly
RS256(case-sensitive) - Check provider supports:
provider.Supports(CryptoCapability.Signing, "RS256")
Issue: CryptographicException: Public key parsing failed
Resolution:
- Ensure public key is DER-encoded SPKI format
- Convert from PEM:
openssl x509 -pubkey -noout -in cert.pem | openssl enc -base64 -d > pubkey.der
Issue: Signature verification always returns false
Resolution:
- Verify algorithm matches
- Ensure message is identical (byte-for-byte)
- Check public key matches private key
- Enable debug logging
References
Related Documentation
- Cryptography & Compliance (platform module) — note: the previously linked
crypto-architecture.mddoes not exist; this is the actual file. - ICryptoProvider Interface
- Provider implementation
- Plugin Manifest (source of truth) — deployed at
/app/etc/crypto-plugins-manifest.json - AirGap Module Architecture
- Sprint Documentation
External Standards
- NIST FIPS 186-4: Digital Signature Standard
- NIST FIPS 180-4: Secure Hash Standard
- RFC 8017: PKCS #1 v2.2
- RFC 6979: Deterministic ECDSA
- RFC 7515: JSON Web Signature
Document Control
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2025-12-23 | StellaOps Platform Team | Initial release with CreateEphemeralVerifier API |
| 1.1 | 2026-05-30 | Doc/code reconciliation | Corrected to match OfflineVerificationCryptoProvider.cs: provider is verification-only; GetSigner signing path is a non-functional stub (generates ephemeral keys, ignores CryptoKeyReference); UpsertSigningKey/RemoveSigningKey throw, GetSigningKeys is empty; ExportPublicJsonWebKey throws; password-hashing Supports()/GetPasswordHasher inconsistency flagged; RFC 6979/BouncyCastle/deterministic-signing removed (no such dependency); enforcement script and CI workflow marked missing/archived; fixed crypto-architecture doc link; documented manifest PS-verification gap. |
License: BUSL-1.1
