1use crate::actions::{ArtifactValue, Share};
14use crate::executor::ExecutionError;
15use rite_model::ArtifactId;
16use rite_sdk::{KeyAlgorithm, KeyCheckValue, KeyId, PublicKeyDer};
17use secrecy::ExposeSecret;
18use std::collections::HashMap;
19use std::hash::BuildHasher;
20
21pub fn resolve_artifact_bytes<'a, S: BuildHasher>(
39 artifacts: &'a HashMap<ArtifactId, ArtifactValue, S>,
40 artifact_id: &ArtifactId,
41 property: Option<&str>,
42) -> Result<&'a [u8], ExecutionError> {
43 let artifact = artifacts.get(artifact_id).ok_or_else(|| {
44 ExecutionError::InvalidParams(format!("Artifact '{artifact_id}' not found"))
45 })?;
46
47 match (artifact, property) {
48 (
50 ArtifactValue::BackendKey {
51 public_key: Some(pub_key),
52 ..
53 },
54 Some("public"),
55 ) => Ok(pub_key.as_bytes()),
56 (
57 ArtifactValue::BackendKey {
58 public_key: None, ..
59 },
60 Some("public"),
61 ) => {
62 let id = artifact_id.as_str();
63 Err(ExecutionError::InvalidParams(format!(
64 "Public key for '{id}' is not exportable from backend"
65 )))
66 }
67 (ArtifactValue::BackendKey { .. }, Some("private")) => {
68 let id = artifact_id.as_str();
69 Err(ExecutionError::InvalidParams(format!(
70 "Cannot access private key from backend-managed key '{id}'"
71 )))
72 }
73
74 (
77 ArtifactValue::BackendKey {
78 check_value: Some(kcv),
79 ..
80 },
81 Some("kcv"),
82 ) => Ok(kcv.as_bytes()),
83 (
84 ArtifactValue::BackendKey {
85 check_value: None,
86 algorithm,
87 ..
88 },
89 Some("kcv"),
90 ) => {
91 let id = artifact_id.as_str();
92 Err(ExecutionError::InvalidParams(format!(
93 "'{id}' is a {algorithm} key, which is named by its public half \
94 rather than by a check value"
95 )))
96 }
97
98 (ArtifactValue::PublicKey(key), None) => Ok(key.as_bytes()),
100
101 (ArtifactValue::WrappedKey(wrapped), None) => Ok(wrapped.data()),
103
104 (ArtifactValue::EncryptedData(encrypted), None) => Ok(encrypted.data()),
107
108 (ArtifactValue::Bytes(bytes), None) => Ok(bytes),
113 (ArtifactValue::Secret(bytes), None) => Ok(bytes.expose_secret()),
114 (ArtifactValue::Text(text), None) => Ok(text.as_bytes()),
115
116 (ArtifactValue::Shares(_), _) => Err(ExecutionError::InvalidParams(format!(
119 "'{artifact_id}' holds shares of a secret, which are read as shares and not as \
120 bytes; only a step that shows or combines shares can name one"
121 ))),
122
123 (ArtifactValue::Certificate(certificate), None) => Ok(certificate.as_bytes()),
126
127 _ => Err(ExecutionError::InvalidParams(format!(
129 "Cannot extract bytes from artifact '{artifact_id}' with property '{property:?}'"
130 ))),
131 }
132}
133
134pub fn resolve_share<'a, S: BuildHasher>(
140 artifacts: &'a HashMap<ArtifactId, ArtifactValue, S>,
141 artifact_id: &ArtifactId,
142 property: Option<&str>,
143) -> Result<&'a Share, ExecutionError> {
144 let artifact = artifacts.get(artifact_id).ok_or_else(|| {
145 ExecutionError::InvalidParams(format!("Artifact '{artifact_id}' not found"))
146 })?;
147 let ArtifactValue::Shares(set) = artifact else {
148 return Err(ExecutionError::InvalidParams(format!(
149 "'{artifact_id}' is not a share; a share is one of a set 'split_secret' made, or \
150 one a custodian typed back"
151 )));
152 };
153 match property {
154 Some(property) => {
155 let index = share_index(property).ok_or_else(|| {
156 ExecutionError::InvalidParams(format!(
157 "'{property}' is not a share of '{artifact_id}'; a share is named \
158 'share_1' through 'share_{}'",
159 set.count()
160 ))
161 })?;
162 set.share(index).ok_or_else(|| {
163 ExecutionError::InvalidParams(format!(
164 "'{artifact_id}' has shares 1 through {}, and share_{index} is not one of them",
165 set.count()
166 ))
167 })
168 }
169 None => set.only().ok_or_else(|| {
170 ExecutionError::InvalidParams(format!(
171 "'{artifact_id}' is a set of {} shares; name one as '{artifact_id}.share_1' \
172 through '{artifact_id}.share_{}'",
173 set.count(),
174 set.count()
175 ))
176 }),
177 }
178}
179
180fn share_index(property: &str) -> Option<u8> {
185 let digits = property.strip_prefix("share_")?;
186 if digits.is_empty() || digits.starts_with('0') || !digits.bytes().all(|b| b.is_ascii_digit()) {
187 return None;
188 }
189 digits.parse::<u8>().ok()
190}
191
192#[derive(Debug, Clone, Copy)]
197#[non_exhaustive]
198pub struct BackendKeyMeta<'a> {
199 pub backend_name: &'a str,
201 pub key_id: &'a KeyId,
203 pub algorithm: KeyAlgorithm,
205 pub public_key: Option<&'a PublicKeyDer>,
207 pub check_value: Option<&'a KeyCheckValue>,
212}
213
214pub fn resolve_backend_key<'a, S: BuildHasher>(
224 artifacts: &'a HashMap<ArtifactId, ArtifactValue, S>,
225 artifact_id: &ArtifactId,
226) -> Result<BackendKeyMeta<'a>, ExecutionError> {
227 let id = artifact_id.as_str();
228 let artifact = artifacts
229 .get(artifact_id)
230 .ok_or_else(|| ExecutionError::InvalidParams(format!("Artifact '{id}' not found")))?;
231
232 match artifact {
233 ArtifactValue::BackendKey {
234 backend_name,
235 key_id,
236 algorithm,
237 public_key,
238 check_value,
239 } => Ok(BackendKeyMeta {
240 backend_name: backend_name.as_str(),
241 key_id,
242 algorithm: *algorithm,
243 public_key: public_key.as_ref(),
244 check_value: check_value.as_ref(),
245 }),
246 _ => Err(ExecutionError::InvalidParams(format!(
247 "Artifact '{id}' is not a backend-managed key"
248 ))),
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use crate::actions::ShareSet;
256
257 #[test]
258 fn resolving_a_bytes_artifact_returns_its_content() {
259 let mut artifacts = HashMap::new();
260 artifacts.insert(
261 ArtifactId::new("ksr"),
262 ArtifactValue::Bytes(b"test content".to_vec()),
263 );
264
265 let result = resolve_artifact_bytes(&artifacts, &ArtifactId::new("ksr"), None).unwrap();
266 assert_eq!(result, b"test content");
267 }
268
269 #[test]
270 fn resolving_a_text_artifact_returns_its_utf8_bytes() {
271 let mut artifacts = HashMap::new();
272 artifacts.insert(
273 ArtifactId::new("usb_drive"),
274 ArtifactValue::Text("USB Drive".to_string()),
275 );
276
277 let result =
278 resolve_artifact_bytes(&artifacts, &ArtifactId::new("usb_drive"), None).unwrap();
279 assert_eq!(result, b"USB Drive");
280 }
281
282 fn two_shares() -> HashMap<ArtifactId, ArtifactValue> {
283 let set = ShareSet::new(
284 2,
285 [
286 Share::new(2, 1, vec![0x11; 4]),
287 Share::new(2, 2, vec![0x22; 4]),
288 ],
289 );
290 HashMap::from([(ArtifactId::new("shares"), ArtifactValue::Shares(set))])
291 }
292
293 #[test]
294 fn a_share_set_has_no_byte_form() {
295 let artifacts = two_shares();
296 for property in [None, Some("share_1")] {
297 let error = resolve_artifact_bytes(&artifacts, &ArtifactId::new("shares"), property)
298 .expect_err("shares are read as shares")
299 .to_string();
300 assert!(error.contains("not as bytes"), "{error}");
301 }
302 }
303
304 #[test]
305 fn a_share_is_named_by_index_and_a_set_of_one_needs_no_name() {
306 let artifacts = two_shares();
307 let id = ArtifactId::new("shares");
308 assert_eq!(
309 resolve_share(&artifacts, &id, Some("share_2"))
310 .unwrap()
311 .index(),
312 2
313 );
314
315 let missing = resolve_share(&artifacts, &id, Some("share_3"))
316 .unwrap_err()
317 .to_string();
318 assert!(missing.contains("share_3 is not one of them"), "{missing}");
319 let misspelt = resolve_share(&artifacts, &id, Some("share_02"))
320 .unwrap_err()
321 .to_string();
322 assert!(misspelt.contains("'share_02' is not a share"), "{misspelt}");
323 let unnamed = resolve_share(&artifacts, &id, None)
324 .unwrap_err()
325 .to_string();
326 assert!(unnamed.contains("is a set of 2 shares"), "{unnamed}");
327
328 let one = ArtifactId::new("typed_back");
329 let mut artifacts = artifacts;
330 artifacts.insert(
331 one.clone(),
332 ArtifactValue::Shares(ShareSet::new(2, [Share::new(2, 3, vec![0x33; 4])])),
333 );
334 assert_eq!(resolve_share(&artifacts, &one, None).unwrap().index(), 3);
335
336 let bytes = ArtifactId::new("bytes");
337 artifacts.insert(bytes.clone(), ArtifactValue::Bytes(vec![1]));
338 let not_a_share = resolve_share(&artifacts, &bytes, None)
339 .unwrap_err()
340 .to_string();
341 assert!(not_a_share.contains("is not a share"), "{not_a_share}");
342 }
343
344 #[test]
345 fn a_share_property_is_digits_from_one_with_no_sign_and_no_leading_zero() {
346 assert_eq!(share_index("share_1"), Some(1));
347 assert_eq!(share_index("share_255"), Some(255));
348 assert_eq!(share_index("share_0"), None);
349 assert_eq!(share_index("share_01"), None);
350 assert_eq!(share_index("share_+1"), None);
351 assert_eq!(share_index("share_256"), None);
352 assert_eq!(share_index("share_"), None);
353 assert_eq!(share_index("share1"), None);
354 assert_eq!(share_index("shares_1"), None);
355 }
356
357 #[test]
358 fn resolving_an_unknown_artifact_fails() {
359 let artifacts: HashMap<ArtifactId, ArtifactValue> = HashMap::new();
360 let result = resolve_artifact_bytes(&artifacts, &ArtifactId::new("missing"), None);
361 assert!(result.is_err());
362 }
363}