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 public_key: pk.as_bytes().to_vec(),
94 private_blob: Vec::new(),
95 })
96 }
97
98 fn generate_key(
100 &self,
101 alias: &str,
102 algorithm: &str,
103 _token: &str,
104 flags: GenerateFlags,
105 ) -> Result<KeyEntry> {
106 let mut search = AttributeMap::default();
107 search.insert(AttributeType::Label, AttrData::Str(alias.into()));
108 let ef = ElementaryFile::find(&self.session, search)?;
109 if flags.contains(GenerateFlags::OVERWRITE) {
110 if ef.len() <= 1 {
111 } else {
113 return Err(HsmError::TooManyObjects(ef.len(), alias.into()).into());
114 }
115 } else if !ef.is_empty() {
116 return Err(HsmError::ObjectExists("<none>".into(), alias.into()).into());
117 }
118
119 let (sk, _) = SpxSecretKey::new_keypair(SphincsPlus::from_str(algorithm)?)?;
120 let app = format!("{}:{}", Self::APPLICATION, sk.algorithm());
121 let skf = ElementaryFile::new(alias.into())
122 .application(app)
123 .private(true);
124 let encoded = Zeroizing::new(sk.to_pem()?);
125 skf.write(&self.session, encoded.as_bytes())?;
126
127 let private_key = if flags.contains(GenerateFlags::EXPORT_PRIVATE) {
128 sk.as_bytes().to_vec()
129 } else {
130 Vec::new()
131 };
132
133 Ok(KeyEntry {
134 alias: alias.into(),
135 hash: Some("".into()),
136 algorithm: sk.algorithm().to_string(),
137 private_blob: Vec::new(),
138 private_key,
139 })
140 }
141
142 fn import_keypair(
144 &self,
145 alias: &str,
146 algorithm: &str,
147 _token: &str,
148 overwrite: bool,
149 public_key: &[u8],
150 private_key: &[u8],
151 ) -> Result<KeyEntry> {
152 let mut search = AttributeMap::default();
153 search.insert(AttributeType::Label, AttrData::Str(alias.into()));
154 let ef = ElementaryFile::find(&self.session, search)?;
155 if overwrite {
156 if ef.len() <= 1 {
157 } else {
159 return Err(HsmError::TooManyObjects(ef.len(), alias.into()).into());
160 }
161 } else if !ef.is_empty() {
162 return Err(HsmError::ObjectExists("<none>".into(), alias.into()).into());
163 }
164
165 let sk = SpxSecretKey::from_bytes(SphincsPlus::from_str(algorithm)?, private_key)?;
166 let pk = SpxPublicKey::from(&sk);
167 if public_key != pk.as_bytes() {
168 return Err(HsmError::KeyError("secret/public key mismatch".into()).into());
169 }
170 let app = format!("{}:{}", Self::APPLICATION, sk.algorithm());
171 let skf = ElementaryFile::new(alias.into())
172 .application(app)
173 .private(true);
174 let encoded = Zeroizing::new(sk.to_pem()?);
175 skf.write(&self.session, encoded.as_bytes())?;
176
177 Ok(KeyEntry {
178 alias: alias.into(),
179 hash: None,
180 algorithm: sk.algorithm().to_string(),
181 private_blob: Vec::new(),
182 private_key: Vec::new(),
183 })
184 }
185
186 fn sign(&self, alias: Option<&str>, key_hash: Option<&str>, message: &[u8]) -> Result<Vec<u8>> {
188 let alias = alias.ok_or(HsmError::NoSearchCriteria)?;
189 if key_hash.is_some() {
190 log::warn!("ignored key_hash {key_hash:?}");
191 }
192 let sk = self.load_key(alias)?;
193 Ok(sk.sign(SpxDomain::None, message)?)
194 }
195
196 fn verify(
198 &self,
199 alias: Option<&str>,
200 key_hash: Option<&str>,
201 message: &[u8],
202 signature: &[u8],
203 ) -> Result<bool> {
204 let alias = alias.ok_or(HsmError::NoSearchCriteria)?;
205 if key_hash.is_some() {
206 log::warn!("ignored key_hash {key_hash:?}");
207 }
208 let sk = self.load_key(alias)?;
209 let pk = SpxPublicKey::from(&sk);
210 match pk.verify(SpxDomain::None, signature, message) {
211 Ok(()) => Ok(true),
212 Err(SpxError::BadSignature) => Ok(false),
213 Err(e) => Err(e.into()),
214 }
215 }
216}