confium_deployment/identity/token.rs
1//! Hardware token descriptors.
2//!
3//! Describes where a key lives in hardware. Standards-only — no vendor
4//! SDKs. Covers YubiKey PIV, YubiKey OpenPGP applet, OpenPGP card v3+,
5//! TPM 2.0.
6
7use serde::{Deserialize, Serialize};
8
9/// A hardware token holding one or more keys.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(tag = "kind", rename_all = "snake_case")]
12pub enum HardwareToken {
13 /// YubiKey PIV applet (PKCS#11 interface).
14 YubiKeyPiv {
15 /// PIV slot identifier (e.g., "9a", "9c", "9e").
16 slot: String,
17 /// PIN policy.
18 pin_policy: PinPolicy,
19 },
20 /// YubiKey OpenPGP applet (OpenPGP card interface).
21 YubiKeyOpenpgp {
22 /// OpenPGP key slot (sig, dec, aut).
23 slot: OpenpgpSlot,
24 },
25 /// Any OpenPGP card v3+ device (YubiKey, Nitrokey, Gnuk).
26 OpenpgpCard {
27 /// Card identifier (typically derived from serial number).
28 card_id: String,
29 /// OpenPGP key slot.
30 slot: OpenpgpSlot,
31 },
32 /// TPM 2.0 sealed key.
33 Tpm {
34 /// TPM persistent handle (e.g., 0x81000001).
35 handle: u32,
36 },
37}
38
39/// PIN entry policy for PIV signing.
40#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42pub enum PinPolicy {
43 /// PIN never required (default).
44 #[default]
45 Never,
46 /// PIN required once per session.
47 Once,
48 /// PIN required every sign operation.
49 Always,
50}
51
52/// OpenPGP card key slot.
53#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
54#[serde(rename_all = "snake_case")]
55pub enum OpenpgpSlot {
56 /// Signature slot (SIG).
57 Signature,
58 /// Decryption slot (DEC).
59 Decryption,
60 /// Authentication slot (AUT).
61 Authentication,
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn yubikey_piv_serializes() {
70 let t = HardwareToken::YubiKeyPiv {
71 slot: "9c".into(),
72 pin_policy: PinPolicy::Always,
73 };
74 let json = serde_json::to_string(&t).unwrap();
75 let recovered: HardwareToken = serde_json::from_str(&json).unwrap();
76 match recovered {
77 HardwareToken::YubiKeyPiv {
78 slot,
79 pin_policy: _,
80 } => assert_eq!(slot, "9c"),
81 _ => panic!("wrong variant"),
82 }
83 }
84}