Skip to main content

rite_runtime/
artifact_resolver.rs

1//! Artifact resolution for ceremony execution.
2//!
3//! This module provides utilities for resolving artifacts stored in the `ExecutionState`
4//! to their byte content or backend key metadata. Artifact references are pre-parsed
5//! at resolution time (see `StepInputs` in rite-model), so no `${...}` parsing
6//! happens here.
7//!
8//! ## Key Functions
9//!
10//! - [`resolve_artifact_bytes`] - Extract byte content from an artifact
11//! - [`resolve_backend_key`] - Get backend key metadata for backend operations
12
13use crate::actions::ArtifactValue;
14use crate::executor::ExecutionError;
15use rite_model::ArtifactId;
16use rite_sdk::{KeyAlgorithm, KeyId};
17use std::collections::HashMap;
18use std::hash::BuildHasher;
19
20/// Resolve an artifact reference to byte content.
21///
22/// # Arguments
23/// * `artifacts` - The artifact store from `ActionContext`
24/// * `artifact_id` - The artifact ID
25/// * `property` - Optional subproperty (e.g., "private" or "public")
26///
27/// # Returns
28/// The resolved bytes, or an error if resolution fails.
29///
30/// # Supported references
31/// - `artifact_id` → full artifact content
32/// - `artifact_id` + "private" → private key from keypair
33/// - `artifact_id` + "public" → public key from keypair
34pub fn resolve_artifact_bytes<S: BuildHasher>(
35    artifacts: &HashMap<ArtifactId, ArtifactValue, S>,
36    artifact_id: &ArtifactId,
37    property: Option<&str>,
38) -> Result<Vec<u8>, ExecutionError> {
39    let artifact = artifacts.get(artifact_id).ok_or_else(|| {
40        ExecutionError::InvalidParams(format!("Artifact '{artifact_id}' not found"))
41    })?;
42
43    match (artifact, property) {
44        // Backend-managed key - only public key is accessible
45        (
46            ArtifactValue::BackendKey {
47                public_key: Some(pub_key),
48                ..
49            },
50            Some("public"),
51        ) => Ok(pub_key.clone()),
52        (
53            ArtifactValue::BackendKey {
54                public_key: None, ..
55            },
56            Some("public"),
57        ) => {
58            let id = artifact_id.as_str();
59            Err(ExecutionError::InvalidParams(format!(
60                "Public key for '{id}' is not exportable from backend"
61            )))
62        }
63        (ArtifactValue::BackendKey { .. }, Some("private")) => {
64            let id = artifact_id.as_str();
65            Err(ExecutionError::InvalidParams(format!(
66                "Cannot access private key from backend-managed key '{id}'"
67            )))
68        }
69
70        // Real public key
71        (ArtifactValue::PublicKey { key_data, .. }, None) => Ok(key_data.clone()),
72
73        // Real wrapped key
74        (ArtifactValue::WrappedKey { data, .. }, None) => Ok(data.clone()),
75
76        // Materials (loaded from files or inline)
77        (ArtifactValue::Bytes(bytes), None) => Ok(bytes.clone()),
78        (ArtifactValue::Text(text), None) => Ok(text.as_bytes().to_vec()),
79
80        // X.509 certificate: return DER bytes
81        (ArtifactValue::Certificate { der }, None) => Ok(der.clone()),
82
83        // Invalid combinations
84        _ => Err(ExecutionError::InvalidParams(format!(
85            "Cannot extract bytes from artifact '{artifact_id}' with property '{property:?}'"
86        ))),
87    }
88}
89
90/// Backend key metadata returned by artifact resolution.
91/// Contains (`backend_name`, `key_id`, `algorithm`, `public_key`).
92pub type BackendKeyMeta<'a> = (&'a str, &'a KeyId, KeyAlgorithm, Option<&'a Vec<u8>>);
93
94/// Resolve an artifact to a backend-managed key reference.
95///
96/// Returns (`backend_name`, `key_id`, `algorithm`, `public_key`) tuple for backend operations.
97/// This is used when actions need to perform operations on backend-managed keys.
98///
99/// # Errors
100/// Returns an error if:
101/// - The artifact doesn't exist
102/// - The artifact is not a `BackendKey` variant
103pub fn resolve_backend_key<'a, S: BuildHasher>(
104    artifacts: &'a HashMap<ArtifactId, ArtifactValue, S>,
105    artifact_id: &ArtifactId,
106) -> Result<BackendKeyMeta<'a>, ExecutionError> {
107    let id = artifact_id.as_str();
108    let artifact = artifacts
109        .get(artifact_id)
110        .ok_or_else(|| ExecutionError::InvalidParams(format!("Artifact '{id}' not found")))?;
111
112    match artifact {
113        ArtifactValue::BackendKey {
114            backend_name,
115            key_id,
116            algorithm,
117            public_key,
118        } => Ok((
119            backend_name.as_str(),
120            key_id,
121            *algorithm,
122            public_key.as_ref(),
123        )),
124        _ => Err(ExecutionError::InvalidParams(format!(
125            "Artifact '{id}' is not a backend-managed key"
126        ))),
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_resolve_bytes() {
136        let mut artifacts = HashMap::new();
137        artifacts.insert(
138            ArtifactId::new("ksr"),
139            ArtifactValue::Bytes(b"test content".to_vec()),
140        );
141
142        let result = resolve_artifact_bytes(&artifacts, &ArtifactId::new("ksr"), None).unwrap();
143        assert_eq!(result, b"test content");
144    }
145
146    #[test]
147    fn test_resolve_text() {
148        let mut artifacts = HashMap::new();
149        artifacts.insert(
150            ArtifactId::new("usb_drive"),
151            ArtifactValue::Text("USB Drive".to_string()),
152        );
153
154        let result =
155            resolve_artifact_bytes(&artifacts, &ArtifactId::new("usb_drive"), None).unwrap();
156        assert_eq!(result, b"USB Drive");
157    }
158
159    #[test]
160    fn test_resolve_not_found() {
161        let artifacts: HashMap<ArtifactId, ArtifactValue> = HashMap::new();
162        let result = resolve_artifact_bytes(&artifacts, &ArtifactId::new("missing"), None);
163        assert!(result.is_err());
164    }
165}