confium_store_pkcs11/
config.rs1use confium_store::backend::Options;
10use confium_store::error::{Error, Result};
11
12pub const OPT_PKCS11_MODULE: &str = "pkcs11_module";
15
16pub const OPT_SLOT_ID: &str = "slot_id";
20
21pub const OPT_PIN: &str = "pin";
25
26pub const OPT_TOKEN_LABEL: &str = "token_label";
29
30#[derive(Debug, Clone)]
35pub struct Config {
36 pub pkcs11_module: String,
38 pub slot_id: u64,
40 pub pin: Option<String>,
42 pub token_label: Option<String>,
45}
46
47impl Config {
48 pub fn from_options(opts: &Options) -> Result<Self> {
57 let pkcs11_module = opts
58 .get(OPT_PKCS11_MODULE)
59 .map(String::as_str)
60 .filter(|s| !s.is_empty())
61 .ok_or_else(|| Error::Wrapped {
62 message: format!(
63 "PKCS#11 backend requires the '{OPT_PKCS11_MODULE}' option \
64 (path to the PKCS#11 shared object)"
65 ),
66 })?
67 .to_string();
68
69 let slot_id = opts
70 .get(OPT_SLOT_ID)
71 .ok_or_else(|| Error::Wrapped {
72 message: format!(
73 "PKCS#11 backend requires the '{OPT_SLOT_ID}' option \
74 (HSM slot id, decimal u64)"
75 ),
76 })
77 .and_then(|raw| {
78 raw.parse::<u64>().map_err(|_| Error::Wrapped {
79 message: format!(
80 "PKCS#11 backend: '{OPT_SLOT_ID}' must be a decimal u64, \
81 got '{raw}'"
82 ),
83 })
84 })?;
85
86 let pin = opts.get(OPT_PIN).filter(|s| !s.is_empty()).cloned();
87 let token_label = opts.get(OPT_TOKEN_LABEL).filter(|s| !s.is_empty()).cloned();
88
89 Ok(Config {
90 pkcs11_module,
91 slot_id,
92 pin,
93 token_label,
94 })
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use std::collections::HashMap;
102
103 fn opts() -> Options {
104 let mut o = Options::new();
105 o.insert(
106 OPT_PKCS11_MODULE.to_string(),
107 "/opt/hsm/libpkcs11.so".into(),
108 );
109 o.insert(OPT_SLOT_ID.to_string(), "0".into());
110 o.insert(OPT_PIN.to_string(), "123456".into());
111 o.insert(OPT_TOKEN_LABEL.to_string(), "confium".into());
112 o
113 }
114
115 #[test]
116 fn parses_all_fields() {
117 let cfg = Config::from_options(&opts()).expect("valid config");
118 assert_eq!(cfg.pkcs11_module, "/opt/hsm/libpkcs11.so");
119 assert_eq!(cfg.slot_id, 0);
120 assert_eq!(cfg.pin.as_deref(), Some("123456"));
121 assert_eq!(cfg.token_label.as_deref(), Some("confium"));
122 }
123
124 #[test]
125 fn pin_and_label_optional() {
126 let mut o = opts();
127 o.remove(OPT_PIN);
128 o.remove(OPT_TOKEN_LABEL);
129 let cfg = Config::from_options(&o).expect("still valid");
130 assert!(cfg.pin.is_none());
131 assert!(cfg.token_label.is_none());
132 }
133
134 #[test]
135 fn empty_pin_treated_as_absent() {
136 let mut o = opts();
137 o.insert(OPT_PIN.to_string(), String::new());
138 let cfg = Config::from_options(&o).expect("empty pin is absent");
139 assert!(cfg.pin.is_none());
140 }
141
142 #[test]
143 fn missing_module_is_error() {
144 let mut o = opts();
145 o.remove(OPT_PKCS11_MODULE);
146 let err = Config::from_options(&o).unwrap_err();
147 assert!(matches!(err, Error::Wrapped { .. }));
148 assert!(format!("{err}").contains(OPT_PKCS11_MODULE));
149 }
150
151 #[test]
152 fn missing_slot_is_error() {
153 let mut o = opts();
154 o.remove(OPT_SLOT_ID);
155 let err = Config::from_options(&o).unwrap_err();
156 assert!(format!("{err}").contains(OPT_SLOT_ID));
157 }
158
159 #[test]
160 fn non_numeric_slot_is_error() {
161 let mut o = opts();
162 o.insert(OPT_SLOT_ID.to_string(), "not-a-number".into());
163 let err = Config::from_options(&o).unwrap_err();
164 assert!(format!("{err}").contains("must be a decimal u64"));
165 }
166
167 #[test]
168 fn empty_options_map_errors() {
169 let empty: Options = HashMap::new();
170 let err = Config::from_options(&empty).unwrap_err();
171 assert!(matches!(err, Error::Wrapped { .. }));
172 }
173}