Skip to content

Security: systemslibrarian/PostQuantum.KeyManagement

Security

SECURITY.md

Security Policy

Cryptography software earns trust by being honest about its limits. This document explains how to report a problem and what guarantees the library does — and does not — make today. For the running list of limitations, see KNOWN-GAPS.md. For the precise statement of what we defend against, the threat model, and the ten numbered security invariants the library is designed to hold, see docs/threat-model.md. For the production operational checklist, see docs/deployment.md.

Reporting a vulnerability

Please do not open a public issue for security problems.

  • Use GitHub Security Advisories ("Report a vulnerability") on this repository, or
  • email the maintainer privately.

Please include enough detail to reproduce: affected version, target framework, a minimal repro, and the impact you observed. You will get an acknowledgement, and we will keep you informed as we investigate, fix, and (with credit, if you wish) disclose.

This is a faith-and-craft project — responses are best-effort, but security reports are always triaged first.

Supported versions

While in 0.x preview, only the latest released version receives security fixes. There is no back-porting to older previews before 1.0. The 1.0 backport policy will be published at that release; see future.md for the 1.0 checklist.

Version Supported
0.4.0-preview.*
older previews

What this library protects

  • Confidentiality & integrity of wrapped keys. Content keys are wrapped with AES-256-GCM, an authenticated cipher. A modified or truncated wrapped key fails to unwrap rather than yielding attacker-influenced key material.
  • Strong content keys. 256-bit keys from the platform CSPRNG (RandomNumberGenerator).
  • Brute-force resistance for passphrases. The local provider stretches passphrases with Argon2id (memory-hard), with tunable cost via LocalKekOptions (presets aligned to RFC 9106 / OWASP).
  • Early detection of a wrong passphrase. Keyring metadata (v3) carries a 32-byte HMAC-SHA256 verifier per KEK; Import checks it in constant time so wrong passphrases are rejected up front rather than as a delayed authentication failure. v2 tokens (16-byte truncated verifier) and v1 tokens (no verifier) still import — the constant-time comparison handles either width via a prefix match against the recomputed 32-byte verifier.
  • Hostile-input resistance. Every token decoder uses overflow-safe length arithmetic and caps every length-prefixed field; the keyring decoder additionally caps the number of KEKs. TryDecode overloads exist for inputs from untrusted sources.
  • Boundary validation. Empty passphrases and obviously-malformed inputs are rejected with clear ArgumentExceptions at the library boundary, before any cryptographic work runs.
  • Concurrent safety. LocalContentKeyProvider documents and tests that rotation, wrap, and unwrap are safe under concurrent use; a rotating thread cannot dispose a KEK in use elsewhere.
  • Safe diagnostic output. The records that carry byte arrays override ToString() to redact byte content as <NN bytes>. Log lines that include these records cannot leak ciphertext or the keyring verifier.
  • Cross-platform atomic file persistence. FileKeyringStore uses File.Replace (POSIX rename(2)) with a bounded retry on Windows-specific IOException from concurrent readers, so the single-writer + many-readers production model documented in docs/deployment.md is race-free in practice.
  • Bounded exposure of plaintext keys. ContentKey zeroes its buffer on Dispose; derived passphrase bytes and intermediate copies are zeroed after use.

What it does NOT protect against (today)

  • A compromised host. If an attacker can read your process memory or your passphrase as it is entered, no library can save the keys in use at that moment.
  • Weak passphrases. Argon2id raises the cost of guessing; it cannot rescue a low-entropy secret.
  • Harvest-now-decrypt-later against asymmetric wrapping. The current release has no asymmetric KEM at all (local wrapping is symmetric). When cloud/asymmetric wrapping arrives, classical RSA/ECC wrapping would be quantum-vulnerable until a PQ KEM (e.g. ML-KEM) or hybrid mode lands. See KNOWN-GAPS.md.
  • Passphrase storage. Storing passphrases safely (env var, secret manager, prompt) is the caller's responsibility. The exported keyring metadata is non-secret and safe to persist next to your data — but the passphrases that derive the KEKs must be in a real secret store. docs/deployment.md walks through the operational shape.
  • External audit. This library has been written with care, automated tests, static analysers, a hostile-input test suite, and a published threat model — but it has not yet had a third-party cryptographic review. That work is tracked in future.md.

Recommended Argon2id profile in production

LocalKekOptions ships four presets aligned to RFC 9106 and OWASP. For the stated threat model — an attacker who has obtained the persisted keyring metadata (salts + parameters + verifier) and is mounting an offline GPU/ASIC-accelerated passphrase guess — the picks are:

Use case Preset Why
Production floor (general server-side) Moderate 256 MiB / 4 iterations / parallelism 4. Buys meaningful margin against GPU/ASIC offline guessing while keeping derivation under ~1 s on modern hardware.
Long-lived master KEKs (root of trust) Sensitive 2 GiB / 1 iteration / parallelism 4 — RFC 9106 §4 first recommendation. Derived once per process or per rotation; pay the cost.
Latency-constrained server Interactive 64 MiB / 3 iterations / parallelism 4. Acceptable when the host is hardened against memory exfiltration and latency budget is the binding constraint.
Constrained host / CI LowMemory OWASP minimum (19 MiB / 2 iterations / parallelism 1). Not a general production setting.

Argon2id derivation is one-shot — once at startup, once per rotation — never on the request path. That means the latency cost is paid rarely and you should pick the highest preset whose one-shot latency you can afford. Tune by measuring on production-shape hardware; whatever you pick is recorded per-KEK in the keyring metadata so future imports reproduce the same KEK.

The LowMemory preset is OWASP's floor for environments where 64 MiB is genuinely unaffordable; it is not the right answer for production unless you have measured and ruled out everything stronger. The library's instance defaults match Interactive, not LowMemory.

Cryptographic dependencies

We do not ship our own implementations of primitives.

Responsible disclosure

We support coordinated disclosure and are happy to credit reporters. Thank you for helping keep users safe.


To God be the glory — 1 Corinthians 10:31.

There aren't any published security advisories