styrene_identity/lib.rs
1//! Deterministic key hierarchy for Styrene mesh nodes.
2//!
3//! One 32-byte root secret derives all protocol-specific keys — RNS,
4//! Yggdrasil, WireGuard, SSH, age, git signing, and per-agent delegation
5//! — via HKDF-SHA256 with domain separation.
6//!
7//! # Usage
8//!
9//! ```rust
10//! use styrene_identity::derive::{KeyDeriver, KeyPurpose};
11//!
12//! let root_secret = [0x42u8; 32]; // in practice, from a signer
13//! let deriver = KeyDeriver::new(&root_secret);
14//!
15//! // Flat-purpose keys (7 protocols)
16//! let git_seed = deriver.derive(KeyPurpose::GitSigning);
17//! let age_key = deriver.derive(KeyPurpose::Age);
18//!
19//! // Parameterized keys (two-level HKDF, structurally collision-free)
20//! let github_ssh = deriver.derive_ssh_user_key("github").unwrap();
21//! let agent_key = deriver.derive_agent_key("omegon-primary").unwrap();
22//! ```
23//!
24//! # Signer tiers
25//!
26//! The [`IdentitySigner`] trait abstracts over four storage backends.
27//! All tiers produce the same root secret — they are different access
28//! paths to the same identity.
29//!
30//! | Tier | Backend | Feature |
31//! |------|---------|---------|
32//! | A | YubiKey FIDO2 hmac-secret | `yubikey` |
33//! | B | Platform secure element | — (planned) |
34//! | C | Credential manager (Bitwarden, Keychain) | — (planned) |
35//! | D | Encrypted file (argon2id + ChaCha20Poly1305) | `file-signer` (default) |
36//!
37//! [`SignerChain`] tries signers in tier order (A→D), using the first available.
38//!
39//! # Feature flags
40//!
41//! | Feature | Default | Enables |
42//! |---------|---------|---------|
43//! | `file-signer` | **yes** | `FileSigner`, `IdentityVault` |
44//! | `signing` | via file-signer | `pubkey` module (ed25519, x25519) |
45//! | `yubikey` | no | `YubiKeySigner` (FIDO2 hmac-secret) |
46//! | `ssh-agent` | no | `StyreneAgent` (SSH agent protocol) |
47//!
48//! # Derivation hierarchy
49//!
50//! ```text
51//! root_secret (32 bytes)
52//! HKDF-Extract(salt="styrene-identity-v1", IKM=root_secret) = PRK
53//! │
54//! ├─ Expand("styrene-rns-encryption-v1") → RNS X25519
55//! ├─ Expand("styrene-rns-signing-v1") → RNS Ed25519 (canonical identity)
56//! ├─ Expand("styrene-yggdrasil-v1") → Yggdrasil Ed25519
57//! ├─ Expand("styrene-wireguard-v1") → WireGuard Curve25519
58//! ├─ Expand("styrene-ssh-host-v1") → SSH host Ed25519
59//! ├─ Expand("styrene-age-v1") → age X25519
60//! ├─ Expand("styrene-git-signing-v1") → git signing Ed25519
61//! │
62//! ├─ SSH user keys (two-level, salt="styrene-identity-ssh-user-v1")
63//! │ └─ Expand(label) → per-host SSH Ed25519
64//! │
65//! └─ Agent keys (two-level, salt="styrene-identity-agent-v1")
66//! └─ Expand(name) → per-agent signing Ed25519
67//! ```
68//!
69//! # Linkability warning
70//!
71//! **All keys derived from one root are cryptographically linked.** This is
72//! by design for attribution and recovery, but it means derived keys cannot
73//! provide anonymity or unlinkability. If you need an identity that cannot be
74//! traced to your primary identity, use [`ephemeral()`](signer::RootSecret::ephemeral) or a
75//! separate identity file. See `docs/unlinkability.md` for the full model.
76//!
77//! ```rust
78//! use styrene_identity::signer::RootSecret;
79//!
80//! // Anonymous: independent CSPRNG root, no link to any persistent identity
81//! let anon = RootSecret::ephemeral();
82//! ```
83//!
84//! # Security
85//!
86//! - All secret material is zeroized on drop ([`RootSecret`], [`KeyDeriver`], [`DerivedKeys`])
87//! - Passphrases and PINs are provided via traits, never environment variables
88//! - File creation uses `O_EXCL` (no TOCTOU race)
89//! - argon2id params exceed OWASP minimums (m=64MiB, t=3, p=1)
90//!
91//! [`IdentitySigner`]: signer::IdentitySigner
92//! [`SignerChain`]: signer::SignerChain
93//! [`RootSecret`]: signer::RootSecret
94//! [`KeyDeriver`]: derive::KeyDeriver
95//! [`DerivedKeys`]: derive::DerivedKeys
96
97pub mod derive;
98pub mod discover;
99#[cfg(feature = "file-signer")]
100pub mod file_signer;
101#[cfg(feature = "keychain")]
102pub mod keychain_signer;
103#[cfg(feature = "signing")]
104pub mod export;
105#[cfg(feature = "signing")]
106pub mod format;
107#[cfg(feature = "signing")]
108pub mod identity;
109#[cfg(feature = "signing")]
110pub mod pubkey;
111pub mod signer;
112#[cfg(feature = "ssh-agent")]
113pub mod ssh_agent;
114#[cfg(feature = "file-signer")]
115pub mod vault;
116#[cfg(feature = "yubikey")]
117pub mod yubikey_signer;
118
119pub use derive::{
120 derive_key, derive_keys, validate_label, DeriveError, DerivedKeys, KeyDeriver, KeyPurpose,
121};
122pub use discover::{discover, DiscoveredIdentity};
123#[cfg(feature = "signing")]
124pub use export::AllPublicKeys;
125#[cfg(feature = "signing")]
126pub use identity::{
127 identity_hash, identity_pubkey, identity_sign, identity_verify, IdentityInfo, PublicIdentity,
128 SignedAttestation, IDENTITY_HASH_BYTES,
129};
130pub use signer::{IdentitySigner, SignerChain, SignerError, SignerTier};