ZKAC/docs/SECURITY.md
2026-04-09 20:11:46 +02:00

8.3 KiB

Security model and audit notes (ZKAC 0.2)

This document summarizes the design, residual risks, and recommendations for operators integrating ZKAC. It is not a substitute for independent review before high-assurance deployment.

Goals

  • Authentication: Only holders of a valid BBS+ credential for a registered role can complete verify_auth for that role.
  • Server identity: The server proves its long-term identity to the client via a Schnorr signature over the session transcript; clients verify against a pinned public key. This prevents MITM attacks without requiring TLS.
  • Confidentiality & integrity: Session payloads are authenticated-encrypted (ChaCha20-Poly1305) with a key derived from an ephemeral X25519 handshake.
  • Replay resistance: Duplicate ciphertexts in a direction are rejected (sliding window + monotonic counter).
  • Unlinkability (credential layer): BBS+ presentations are unlinkable across sessions when the presentation header (the session transcript hash) differs; the verifier learns only the disclosed attributes (opaque role_id, epoch) and validity. Client anonymity is preserved: the client never reveals its long-term key during the handshake.
  • Server cannot forge credentials: The server stores only the issuer public key per role; forging requires the issuer secret key.

Cryptographic components

Layer Primitive Purpose
Transport X25519 ephemeral DH, HKDF-SHA256, ChaCha20-Poly1305 Session keys, AEAD
Identity Schnorr on Ristretto255, BLAKE2b-512 challenge Server identity binding
Credentials BBS+ on BLS12-381 (zkryptium), SHAKE256 ciphersuite Blind issuance, ZK presentations
Role IDs BLAKE2b-512 (truncated to 32 bytes) Opaque role identifiers

Protocol flow

Client                                Server
  |--- init_msg (eph_pk) ------------>|
  |                                   | accept()
  |                                   | prove_identity() → sign(transcript)
  |<-- response_msg + identity_pkt ---|
  | complete DH                       |
  | decrypt + verify server sig       |
  | encrypt BBS+ auth                 |
  |--- encrypted BBS+ auth ---------> |
  |                                   | verify_auth()
  |===== encrypted session ===========>|

Threats considered

Network attacker (passive)

  • Observes ciphertexts; cannot break ChaCha20-Poly1305 or derive session keys without breaking X25519 / HKDF under standard assumptions.

Network attacker (active / MITM)

  • Server impersonation: The server signs the session transcript hash with its long-term Ristretto255 key (prove_identity). The client verifies this signature against the pinned server public key. A MITM running a separate DH exchange produces a different transcript; it cannot forge the server's signature. The client aborts on mismatch.
  • Client impersonation: The BBS+ presentation is bound to the session transcript hash. A MITM cannot relay a presentation from one session to another (different transcripts) or forge one (requires a valid credential from the issuer).
  • Relay attack: A MITM that relays the real server's identity proof to a client fails because the proof is encrypted under the MITM-to-server session keys (not the client-to-MITM keys), and the signature is over the wrong transcript.

Malicious server

  • Can learn opaque role_id, current epoch, and that some valid member authenticated.
  • Cannot forge BBS+ credentials without the issuer secret key.
  • Cannot learn member_secret from presentations under the BBS+ security assumptions.
  • Cannot distinguish which specific member authenticated among valid credential holders (unlinkability holds against the verifier for distinct presentation headers).
  • Cannot learn the client's long-term public key — it is never transmitted.

Malicious client

  • Cannot decrypt others' traffic without session keys.
  • Cannot produce valid auth for a role without a valid credential + correct epoch + registry entry.

Denial of service

  • Auth packet size: Proof length is capped (MAX_BBS_AUTH_PROOF_BYTES, 256 KiB) to bound allocations.
  • Handshake: Fixed 32-byte messages; no variable-length handshake parsing.
  • General packet limits should still be enforced at the application layer (total message size, rate limits).

Key distribution

The server's long-term PublicKey (32-byte Ristretto255 point) functions as a self-authenticating identity — no certificate authority is required. The client must obtain and pin this key before connecting.

Recommended strategies:

  1. Static configuration (default): embed the server public key in client config, environment variable, or CLI flag. Equivalent to WireGuard's [Peer] PublicKey = ....
  2. Trust On First Use (TOFU): accept the server's key on first connection, pin it for subsequent sessions. Risk: first connection is vulnerable.
  3. Out-of-band verification: compare public key fingerprints over a trusted side channel (phone, in-person, encrypted messaging).
  4. Key registry / directory: a trusted service maps names to public keys. Shifts trust to the registry and its authentication channel.

Operational requirements

  1. Issuer secret key: Protect BbsIssuer secret material (HSM, KMS, or encrypted at rest). Compromise = ability to issue arbitrary credentials for that role.
  2. Server long-term key: Protect the Node Keypair secret. Compromise = ability to impersonate the server. Rotate the key and distribute the new public key to clients if compromised.
  3. Member storage: member_secret and finalized Credential material must be protected; loss = re-enrollment required.
  4. Epoch revocation: On compromise or policy change, call set_epoch and re-issue credentials only to legitimate members; old credentials become invalid at verification time.
  5. Registry integrity: The server's (role_id → public key, epoch) mapping must be integrity-protected (trusted storage or signed updates), or attackers could swap keys or epochs.
  6. Role ID privacy: role_id is a hash of the role name only if you use role_id("myrole"); treat role names as secrets if enumeration is a concern, or derive role IDs with an additional secret salt known to members.

Implementation notes (audit checklist)

  • BBS+ proof verification uses the same header and presentation binding as proof generation (verify_presentation in Rust).
  • Session transcript is included in the presentation via present(transcript_hash).
  • Server identity proof: Schnorr signature over transcript_hash, verified against pinned public key before BBS+ auth proceeds.
  • Schnorr nonce is deterministic (H(sk || msg)) — no dependence on RNG quality at signing time.
  • Replay protection is symmetric per direction in Session.
  • Constant-time comparisons are used where critical in transport/replay paths (subtle crate).
  • Client long-term key is never transmitted, preserving BBS+ unlinkability.
  • External: Python bindings surface raw bytes; callers must not log secrets (secret_key_bytes, member_secret, prover_blind).
  • External: Use secure randomness from the OS (library uses OS RNG for key generation paths exposed in Rust).

Design decisions

  • Server-only identity proof: Only the server signs the transcript. Adding client long-term signing would break BBS+ unlinkability (the server could correlate sessions by client public key). Client authentication is handled entirely by the anonymous BBS+ credential.
  • Deterministic Schnorr nonces: The signing nonce is derived as H("zkac-schnorr-nonce" || sk || msg), eliminating a class of RNG-failure attacks (cf. PS3 ECDSA, Sony 2010). Same key + same message = same signature.

Known limitations

  • No post-quantum primitives: classical security assumptions only.
  • Epoch granularity: Revocation is coarse (epoch bump); plan issuance and rotation policy accordingly.
  • zkryptium dependency: Security follows the underlying crate and BLS12-381/BBS+ standards; keep dependencies updated.
  • Key distribution: The library provides the cryptographic mechanism; initial key distribution is an application-layer responsibility.

Reporting issues

Report security-sensitive findings through your project's private disclosure channel (configure SECURITY.md contact or GitHub security advisories when the repository is public).