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, KeyCheckValue, KeyId, PublicKeyDer};
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
34/// - `artifact_id` + "kcv" → key check value of a symmetric key
35pub fn resolve_artifact_bytes<S: BuildHasher>(
36    artifacts: &HashMap<ArtifactId, ArtifactValue, S>,
37    artifact_id: &ArtifactId,
38    property: Option<&str>,
39) -> Result<Vec<u8>, ExecutionError> {
40    let artifact = artifacts.get(artifact_id).ok_or_else(|| {
41        ExecutionError::InvalidParams(format!("Artifact '{artifact_id}' not found"))
42    })?;
43
44    match (artifact, property) {
45        // Backend-managed key - only public key is accessible
46        (
47            ArtifactValue::BackendKey {
48                public_key: Some(pub_key),
49                ..
50            },
51            Some("public"),
52        ) => Ok(pub_key.as_bytes().to_vec()),
53        (
54            ArtifactValue::BackendKey {
55                public_key: None, ..
56            },
57            Some("public"),
58        ) => {
59            let id = artifact_id.as_str();
60            Err(ExecutionError::InvalidParams(format!(
61                "Public key for '{id}' is not exportable from backend"
62            )))
63        }
64        (ArtifactValue::BackendKey { .. }, Some("private")) => {
65            let id = artifact_id.as_str();
66            Err(ExecutionError::InvalidParams(format!(
67                "Cannot access private key from backend-managed key '{id}'"
68            )))
69        }
70
71        // The check value of a symmetric key, as its three raw bytes, so
72        // `${artifact.kek.kcv | hex}` reads the way an operator says it.
73        (
74            ArtifactValue::BackendKey {
75                check_value: Some(kcv),
76                ..
77            },
78            Some("kcv"),
79        ) => Ok(kcv.as_bytes().to_vec()),
80        (
81            ArtifactValue::BackendKey {
82                check_value: None,
83                algorithm,
84                ..
85            },
86            Some("kcv"),
87        ) => {
88            let id = artifact_id.as_str();
89            Err(ExecutionError::InvalidParams(format!(
90                "'{id}' is a {algorithm} key, which is named by its public half \
91                 rather than by a check value"
92            )))
93        }
94
95        // Real public key
96        (ArtifactValue::PublicKey(key), None) => Ok(key.as_bytes().to_vec()),
97
98        // Real wrapped key
99        (ArtifactValue::WrappedKey(wrapped), None) => Ok(wrapped.data().to_vec()),
100
101        // Materials (loaded from files or inline)
102        (ArtifactValue::Bytes(bytes), None) => Ok(bytes.clone()),
103        (ArtifactValue::Text(text), None) => Ok(text.as_bytes().to_vec()),
104
105        // X.509 certificate: the whole certificate, not the key inside it.
106        // `issue_certificate` reads an issuer certificate through here.
107        (ArtifactValue::Certificate(certificate), None) => Ok(certificate.as_bytes().to_vec()),
108
109        // Invalid combinations
110        _ => Err(ExecutionError::InvalidParams(format!(
111            "Cannot extract bytes from artifact '{artifact_id}' with property '{property:?}'"
112        ))),
113    }
114}
115
116/// What an action needs to know about a backend-managed key.
117///
118/// Borrowed from the artifact store rather than cloned: a caller that needs an
119/// owned `KeyId` or key past the borrow clones the field it needs.
120#[derive(Debug, Clone, Copy)]
121#[non_exhaustive]
122pub struct BackendKeyMeta<'a> {
123    /// Backend that owns the key. A step running on another one is an error.
124    pub backend_name: &'a str,
125    /// The reference that backend answers to.
126    pub key_id: &'a KeyId,
127    /// Algorithm, for choosing a signature scheme and for the transcript.
128    pub algorithm: KeyAlgorithm,
129    /// Public half, absent for a key the backend does not export.
130    pub public_key: Option<&'a PublicKeyDer>,
131    /// Check value, present for a symmetric key and absent for a keypair.
132    ///
133    /// What names a key with no public half, so a step recording which key it
134    /// operated on has something to record for either form.
135    pub check_value: Option<&'a KeyCheckValue>,
136}
137
138/// Resolve an artifact to a backend-managed key reference.
139///
140/// Used when an action operates on a key the backend holds, which is every
141/// action that needs a private key.
142///
143/// # Errors
144/// Returns an error if:
145/// - The artifact doesn't exist
146/// - The artifact is not a `BackendKey` variant
147pub fn resolve_backend_key<'a, S: BuildHasher>(
148    artifacts: &'a HashMap<ArtifactId, ArtifactValue, S>,
149    artifact_id: &ArtifactId,
150) -> Result<BackendKeyMeta<'a>, ExecutionError> {
151    let id = artifact_id.as_str();
152    let artifact = artifacts
153        .get(artifact_id)
154        .ok_or_else(|| ExecutionError::InvalidParams(format!("Artifact '{id}' not found")))?;
155
156    match artifact {
157        ArtifactValue::BackendKey {
158            backend_name,
159            key_id,
160            algorithm,
161            public_key,
162            check_value,
163        } => Ok(BackendKeyMeta {
164            backend_name: backend_name.as_str(),
165            key_id,
166            algorithm: *algorithm,
167            public_key: public_key.as_ref(),
168            check_value: check_value.as_ref(),
169        }),
170        _ => Err(ExecutionError::InvalidParams(format!(
171            "Artifact '{id}' is not a backend-managed key"
172        ))),
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_resolve_bytes() {
182        let mut artifacts = HashMap::new();
183        artifacts.insert(
184            ArtifactId::new("ksr"),
185            ArtifactValue::Bytes(b"test content".to_vec()),
186        );
187
188        let result = resolve_artifact_bytes(&artifacts, &ArtifactId::new("ksr"), None).unwrap();
189        assert_eq!(result, b"test content");
190    }
191
192    #[test]
193    fn test_resolve_text() {
194        let mut artifacts = HashMap::new();
195        artifacts.insert(
196            ArtifactId::new("usb_drive"),
197            ArtifactValue::Text("USB Drive".to_string()),
198        );
199
200        let result =
201            resolve_artifact_bytes(&artifacts, &ArtifactId::new("usb_drive"), None).unwrap();
202        assert_eq!(result, b"USB Drive");
203    }
204
205    #[test]
206    fn test_resolve_not_found() {
207        let artifacts: HashMap<ArtifactId, ArtifactValue> = HashMap::new();
208        let result = resolve_artifact_bytes(&artifacts, &ArtifactId::new("missing"), None);
209        assert!(result.is_err());
210    }
211}