1use acorn::{GenerateFlags, KeyEntry, KeyInfo, SpxInterface};
6use anyhow::Result;
7use cryptoki::session::Session;
8use sphincsplus::{DecodeKey, EncodeKey};
9use sphincsplus::{SphincsPlus, SpxDomain, SpxError, SpxPublicKey, SpxSecretKey};
10use std::rc::Rc;
11use std::str::FromStr;
12use zeroize::Zeroizing;
13
14use crate::error::HsmError;
15use crate::util::attribute::{AttrData, AttributeMap, AttributeType};
16use crate::util::ef::ElementaryFile;
17
18pub struct SpxEf {
26 session: Rc<Session>,
27}
28
29impl SpxEf {
30 const APPLICATION: &'static str = "hsmtool-spx";
31
32 pub fn new(session: Rc<Session>) -> Box<Self> {
33 Box::new(Self { session })
34 }
35
36 fn load_key(&self, alias: &str) -> Result<SpxSecretKey> {
37 let mut search = AttributeMap::default();
38 search.insert(AttributeType::Label, AttrData::Str(alias.into()));
39 let mut ef = ElementaryFile::find(&self.session, search)?;
40 if ef.is_empty() {
41 return Err(HsmError::ObjectNotFound(alias.into()).into());
42 } else if ef.len() > 1 {
43 return Err(HsmError::TooManyObjects(ef.len(), alias.into()).into());
44 }
45 let ef = ef.remove(0);
46 if let Some(app) = &ef.application {
47 match app.split_once(':') {
48 Some((Self::APPLICATION, _algo)) => {
49 let data = Zeroizing::new(String::from_utf8(ef.read(&self.session)?)?);
50 return Ok(SpxSecretKey::from_pem(data.as_str())?);
51 }
52 Some((_, _)) | None => {
53 return Err(HsmError::UnknownApplication(app.into()).into());
54 }
55 }
56 }
57 Err(HsmError::UnknownApplication("<none>".into()).into())
58 }
59}
60
61impl SpxInterface for SpxEf {
62 fn get_version(&self) -> Result<String> {
64 Ok(String::from("PKCS#11 ElementaryFiles 0.0.1"))
65 }
66
67 fn list_keys(&self) -> Result<Vec<KeyEntry>> {
69 let mut result = Vec::new();
70 for file in ElementaryFile::list(&self.session)? {
71 if let Some(app) = file.application
72 && let Some((Self::APPLICATION, algo)) = app.split_once(':')
73 {
74 result.push(KeyEntry {
75 alias: file.name.clone(),
76 hash: None,
77 algorithm: algo.into(),
78 ..Default::default()
79 });
80 }
81 }
82 Ok(result)
83 }
84
85 fn get_key_info(&self, alias: &str) -> Result<KeyInfo> {
87 let sk = self.load_key(alias)?;
88 let pk = SpxPublicKey::from(&sk);
89
90 Ok(KeyInfo {
91 hash: "".into(),
92 algorithm: pk.algorithm().to_string(),
93 domain: None,
94 public_key: pk.as_bytes().to_vec(),
95 private_blob: Vec::new(),
96 })
97 }
98
99 fn generate_key(
101 &self,
102 alias: &str,
103 algorithm: &str,
104 _domain: SpxDomain,
105 _token: &str,
106 flags: GenerateFlags,
107 ) -> Result<KeyEntry> {
108 let mut search = AttributeMap::default();
109 search.insert(AttributeType::Label, AttrData::Str(alias.into()));
110 let ef = ElementaryFile::find(&self.session, search)?;
111 if flags.contains(GenerateFlags::OVERWRITE) {
112 if ef.len() <= 1 {
113 } else {
115 return Err(HsmError::TooManyObjects(ef.len(), alias.into()).into());
116 }
117 } else if !ef.is_empty() {
118 return Err(HsmError::ObjectExists("<none>".into(), alias.into()).into());
119 }
120
121 let (sk, _) = SpxSecretKey::new_keypair(SphincsPlus::from_str(algorithm)?)?;
122 let app = format!("{}:{}", Self::APPLICATION, sk.algorithm());
123 let skf = ElementaryFile::new(alias.into())
124 .application(app)
125 .private(true);
126 let encoded = Zeroizing::new(sk.to_pem()?);
127 skf.write(&self.session, encoded.as_bytes())?;
128
129 let private_key = if flags.contains(GenerateFlags::EXPORT_PRIVATE) {
130 sk.as_bytes().to_vec()
131 } else {
132 Vec::new()
133 };
134
135 Ok(KeyEntry {
136 alias: alias.into(),
137 hash: Some("".into()),
138 algorithm: sk.algorithm().to_string(),
139 domain: None,
140 private_blob: Vec::new(),
141 private_key,
142 })
143 }
144
145 fn import_keypair(
147 &self,
148 alias: &str,
149 algorithm: &str,
150 _domain: SpxDomain,
151 _token: &str,
152 overwrite: bool,
153 public_key: &[u8],
154 private_key: &[u8],
155 ) -> Result<KeyEntry> {
156 let mut search = AttributeMap::default();
157 search.insert(AttributeType::Label, AttrData::Str(alias.into()));
158 let ef = ElementaryFile::find(&self.session, search)?;
159 if overwrite {
160 if ef.len() <= 1 {
161 } else {
163 return Err(HsmError::TooManyObjects(ef.len(), alias.into()).into());
164 }
165 } else if !ef.is_empty() {
166 return Err(HsmError::ObjectExists("<none>".into(), alias.into()).into());
167 }
168
169 let sk = SpxSecretKey::from_bytes(SphincsPlus::from_str(algorithm)?, private_key)?;
170 let pk = SpxPublicKey::from(&sk);
171 if public_key != pk.as_bytes() {
172 return Err(HsmError::KeyError("secret/public key mismatch".into()).into());
173 }
174 let app = format!("{}:{}", Self::APPLICATION, sk.algorithm());
175 let skf = ElementaryFile::new(alias.into())
176 .application(app)
177 .private(true);
178 let encoded = Zeroizing::new(sk.to_pem()?);
179 skf.write(&self.session, encoded.as_bytes())?;
180
181 Ok(KeyEntry {
182 alias: alias.into(),
183 hash: None,
184 algorithm: sk.algorithm().to_string(),
185 domain: None,
186 private_blob: Vec::new(),
187 private_key: Vec::new(),
188 })
189 }
190
191 fn sign(
193 &self,
194 alias: Option<&str>,
195 key_hash: Option<&str>,
196 domain: SpxDomain,
197 message: &[u8],
198 ) -> Result<Vec<u8>> {
199 let alias = alias.ok_or(HsmError::NoSearchCriteria)?;
200 if key_hash.is_some() {
201 log::warn!("ignored key_hash {key_hash:?}");
202 }
203 let sk = self.load_key(alias)?;
204 Ok(sk.sign(domain, message)?)
205 }
206
207 fn verify(
209 &self,
210 alias: Option<&str>,
211 key_hash: Option<&str>,
212 domain: SpxDomain,
213 message: &[u8],
214 signature: &[u8],
215 ) -> Result<bool> {
216 let alias = alias.ok_or(HsmError::NoSearchCriteria)?;
217 if key_hash.is_some() {
218 log::warn!("ignored key_hash {key_hash:?}");
219 }
220 let sk = self.load_key(alias)?;
221 let pk = SpxPublicKey::from(&sk);
222 match pk.verify(domain, signature, message) {
223 Ok(()) => Ok(true),
224 Err(SpxError::BadSignature) => Ok(false),
225 Err(e) => Err(e.into()),
226 }
227 }
228}