1use crate::actions::ArtifactValue;
7use crate::artifact_resolver::resolve_artifact_bytes;
8use crate::executor::ExecutionError;
9use crate::state::HandlerContext;
10use base16ct::lower::encode_string as hex_encode_string;
11use base32ct::{Base32Unpadded, Encoding as Base32Encoding};
12use base64ct::{Base64, Encoding as Base64Encoding};
13use rite_model::expression::{
14 ExprValue, Expression, Literal, PipeStage, RefType, Reference, StringPart, Value,
15 literal_to_json,
16};
17use rite_model::{ArtifactId, ParamId};
18use sha2::{Digest, Sha256, Sha384, Sha512};
19
20pub fn evaluate(expr: &Expression, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
30 match expr {
31 Expression::Reference(ref_) => evaluate_reference(ref_, ctx),
32 Expression::Literal(lit) => Ok(evaluate_literal(lit)),
33 Expression::Pipeline { source, stages } => {
34 let mut value = evaluate(source, ctx)?;
35 for stage in stages {
36 value = apply_pipe_stage(&value, stage, ctx)?;
37 }
38 Ok(value)
39 }
40 }
41}
42
43fn evaluate_reference(ref_: &Reference, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
45 match ref_.ref_type {
46 RefType::Param => evaluate_param_ref(ref_, ctx),
47 RefType::Artifact => evaluate_artifact_ref(ref_, ctx),
48 RefType::Role => Ok(Value::String(ref_.name.clone())),
51 }
52}
53
54fn evaluate_param_ref(ref_: &Reference, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
56 let param_id = ParamId::new(&ref_.name);
57 let value = ctx.params.get(¶m_id).ok_or_else(|| {
58 ExecutionError::InvalidParams(format!("Unknown parameter: {}", ref_.name))
59 })?;
60
61 json_to_value(value)
62}
63
64fn json_to_value(json: &serde_json::Value) -> Result<Value, ExecutionError> {
66 match json {
67 serde_json::Value::String(s) => Ok(Value::String(s.clone())),
68 serde_json::Value::Number(n) => {
69 if let Some(i) = n.as_i64() {
70 Ok(Value::Integer(i))
71 } else if let Some(f) = n.as_f64() {
72 #[allow(clippy::cast_possible_truncation)]
73 Ok(Value::Integer(f as i64))
74 } else {
75 Err(ExecutionError::InvalidParams(
76 "Number too large".to_string(),
77 ))
78 }
79 }
80 serde_json::Value::Bool(b) => Ok(Value::Boolean(*b)),
81 serde_json::Value::Null => Ok(Value::Null),
82 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
83 Err(ExecutionError::InvalidParams(
84 "Cannot use array or object as expression value".to_string(),
85 ))
86 }
87 }
88}
89
90fn evaluate_artifact_ref(ref_: &Reference, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
92 let artifact_id = ArtifactId::new(&ref_.name);
93 let artifact = ctx.get_artifact(&artifact_id).ok_or_else(|| {
94 ExecutionError::InvalidParams(format!("Artifact '{}' not found", ref_.name))
95 })?;
96
97 match artifact {
98 ArtifactValue::Text(text) => {
100 if let Some(property) = &ref_.property {
101 return Err(ExecutionError::InvalidParams(format!(
102 "Property access '.{property}' not supported for text artifacts"
103 )));
104 }
105 Ok(Value::String(text.clone()))
106 }
107
108 ArtifactValue::Bytes(bytes) => {
110 if let Some(property) = &ref_.property {
112 return Err(ExecutionError::InvalidParams(format!(
113 "Property access '.{property}' not supported for binary artifacts"
114 )));
115 }
116 Ok(Value::Bytes(bytes.clone()))
117 }
118
119 _ => {
121 let bytes =
122 resolve_artifact_bytes(ctx.artifacts, &artifact_id, ref_.property.as_deref())?;
123 Ok(Value::Bytes(bytes))
124 }
125 }
126}
127
128fn evaluate_literal(lit: &Literal) -> Value {
130 match lit {
131 Literal::String(s) => Value::String(s.clone()),
132 Literal::Integer(i) => Value::Integer(*i),
133 #[allow(clippy::cast_possible_truncation)]
134 Literal::Float(f) => Value::Integer(*f as i64), Literal::Boolean(b) => Value::Boolean(*b),
136 Literal::Null => Value::Null,
137 }
138}
139
140fn apply_pipe_stage(
142 input: &Value,
143 stage: &PipeStage,
144 ctx: &HandlerContext,
145) -> Result<Value, ExecutionError> {
146 match stage.function.as_str() {
147 "sha256" => apply_sha256(input),
149 "sha384" => apply_sha384(input),
150 "sha512" => apply_sha512(input),
151
152 "hex" => apply_hex(input),
154 "base32" => apply_base32(input),
155 "base64" => apply_base64(input),
156
157 "upper" => apply_upper(input),
159 "lower" => apply_lower(input),
160 "substr" => apply_substr(input, &stage.args, ctx),
161
162 "concat" => apply_concat(&stage.args, ctx),
164
165 unknown => Err(ExecutionError::InvalidParams(format!(
166 "Unknown function: {unknown}"
167 ))),
168 }
169}
170
171fn apply_sha256(input: &Value) -> Result<Value, ExecutionError> {
172 let bytes = input.as_bytes().ok_or_else(|| {
173 ExecutionError::InvalidParams(format!(
174 "sha256 expects bytes or string, got {}",
175 input.type_name()
176 ))
177 })?;
178
179 let mut hasher = Sha256::new();
180 hasher.update(&bytes);
181 let result = hasher.finalize();
182
183 Ok(Value::Bytes(result.to_vec()))
184}
185
186fn apply_sha384(input: &Value) -> Result<Value, ExecutionError> {
187 let bytes = input.as_bytes().ok_or_else(|| {
188 ExecutionError::InvalidParams(format!(
189 "sha384 expects bytes or string, got {}",
190 input.type_name()
191 ))
192 })?;
193
194 let mut hasher = Sha384::new();
195 hasher.update(&bytes);
196 let result = hasher.finalize();
197
198 Ok(Value::Bytes(result.to_vec()))
199}
200
201fn apply_sha512(input: &Value) -> Result<Value, ExecutionError> {
202 let bytes = input.as_bytes().ok_or_else(|| {
203 ExecutionError::InvalidParams(format!(
204 "sha512 expects bytes or string, got {}",
205 input.type_name()
206 ))
207 })?;
208
209 let mut hasher = Sha512::new();
210 hasher.update(&bytes);
211 let result = hasher.finalize();
212
213 Ok(Value::Bytes(result.to_vec()))
214}
215
216fn apply_hex(input: &Value) -> Result<Value, ExecutionError> {
217 let bytes = input.as_bytes().ok_or_else(|| {
218 ExecutionError::InvalidParams(format!(
219 "hex expects bytes or string, got {}",
220 input.type_name()
221 ))
222 })?;
223
224 let hex_string = hex_encode(&bytes);
225 Ok(Value::String(hex_string))
226}
227
228fn apply_base32(input: &Value) -> Result<Value, ExecutionError> {
229 let bytes = input.as_bytes().ok_or_else(|| {
230 ExecutionError::InvalidParams(format!(
231 "base32 expects bytes or string, got {}",
232 input.type_name()
233 ))
234 })?;
235
236 let encoded = Base32Unpadded::encode_string(&bytes).to_uppercase();
238 Ok(Value::String(encoded))
239}
240
241fn apply_base64(input: &Value) -> Result<Value, ExecutionError> {
242 let bytes = input.as_bytes().ok_or_else(|| {
243 ExecutionError::InvalidParams(format!(
244 "base64 expects bytes or string, got {}",
245 input.type_name()
246 ))
247 })?;
248
249 let encoded = Base64::encode_string(&bytes);
250 Ok(Value::String(encoded))
251}
252
253fn hex_encode(bytes: &[u8]) -> String {
255 hex_encode_string(bytes)
256}
257
258fn apply_upper(input: &Value) -> Result<Value, ExecutionError> {
259 let s = input.as_string().ok_or_else(|| {
260 ExecutionError::InvalidParams(format!("upper expects string, got {}", input.type_name()))
261 })?;
262
263 Ok(Value::String(s.to_uppercase()))
264}
265
266fn apply_lower(input: &Value) -> Result<Value, ExecutionError> {
267 let s = input.as_string().ok_or_else(|| {
268 ExecutionError::InvalidParams(format!("lower expects string, got {}", input.type_name()))
269 })?;
270
271 Ok(Value::String(s.to_lowercase()))
272}
273
274fn apply_substr(
275 input: &Value,
276 args: &[Expression],
277 ctx: &HandlerContext,
278) -> Result<Value, ExecutionError> {
279 let s = input.as_string().ok_or_else(|| {
280 ExecutionError::InvalidParams(format!("substr expects string, got {}", input.type_name()))
281 })?;
282
283 let [start_arg, len_arg] = args else {
284 return Err(ExecutionError::InvalidParams(
285 "substr requires 2 arguments: start and length".to_string(),
286 ));
287 };
288
289 let start_i64 = evaluate(start_arg, ctx)?
290 .as_integer()
291 .ok_or_else(|| ExecutionError::InvalidParams("substr start must be integer".to_string()))?;
292
293 let len_i64 = evaluate(len_arg, ctx)?.as_integer().ok_or_else(|| {
294 ExecutionError::InvalidParams("substr length must be integer".to_string())
295 })?;
296
297 let start = usize::try_from(start_i64.max(0)).unwrap_or(0).min(s.len());
299 let len = usize::try_from(len_i64.max(0)).unwrap_or(0);
300 let end = start.saturating_add(len).min(s.len());
301
302 Ok(Value::String(
303 s.get(start..end).unwrap_or_default().to_string(),
304 ))
305}
306
307fn apply_concat(args: &[Expression], ctx: &HandlerContext) -> Result<Value, ExecutionError> {
308 let mut result = Vec::new();
309
310 for arg in args {
311 let value = evaluate(arg, ctx)?;
312 let bytes = value.as_bytes().ok_or_else(|| {
313 ExecutionError::InvalidParams(format!(
314 "concat expects bytes arguments, got {}",
315 value.type_name()
316 ))
317 })?;
318 result.extend(bytes);
319 }
320
321 Ok(Value::Bytes(result))
322}
323
324pub fn evaluate_expr_value(
337 value: &ExprValue,
338 ctx: &HandlerContext,
339) -> Result<serde_json::Value, ExecutionError> {
340 match value {
341 ExprValue::Literal(lit) => Ok(literal_to_json(lit)),
342 ExprValue::Expr(expr) => {
343 let result = evaluate(expr, ctx)?;
344 Ok(value_to_json(&result))
345 }
346 ExprValue::Interpolated(parts) => {
347 let mut result = String::new();
348 for part in parts {
349 match part {
350 StringPart::Literal(s) => result.push_str(s),
351 StringPart::Expr(expr) => {
352 let val = evaluate(expr, ctx)?;
353 result.push_str(&val.to_string());
354 }
355 }
356 }
357 Ok(serde_json::Value::String(result))
358 }
359 ExprValue::Object(map) => {
360 let mut result = serde_json::Map::new();
361 for (k, v) in map {
362 result.insert(k.clone(), evaluate_expr_value(v, ctx)?);
363 }
364 Ok(serde_json::Value::Object(result))
365 }
366 ExprValue::Array(arr) => {
367 let result: Result<Vec<_>, _> =
368 arr.iter().map(|v| evaluate_expr_value(v, ctx)).collect();
369 Ok(serde_json::Value::Array(result?))
370 }
371 }
372}
373
374pub fn evaluate_expr_value_to_string(
378 value: &ExprValue,
379 ctx: &HandlerContext,
380) -> Result<String, ExecutionError> {
381 match value {
382 ExprValue::Literal(Literal::String(s)) => Ok(s.clone()),
383 ExprValue::Literal(lit) => {
384 let json = literal_to_json(lit);
386 Ok(match json {
387 serde_json::Value::String(s) => s,
388 serde_json::Value::Number(n) => n.to_string(),
389 serde_json::Value::Bool(b) => b.to_string(),
390 serde_json::Value::Null => "null".to_string(),
391 _ => json.to_string(),
392 })
393 }
394 ExprValue::Expr(expr) => {
395 let result = evaluate(expr, ctx)?;
396 Ok(result.to_string())
397 }
398 ExprValue::Interpolated(parts) => {
399 let mut result = String::new();
400 for part in parts {
401 match part {
402 StringPart::Literal(s) => result.push_str(s),
403 StringPart::Expr(expr) => {
404 let val = evaluate(expr, ctx)?;
405 result.push_str(&val.to_string());
406 }
407 }
408 }
409 Ok(result)
410 }
411 ExprValue::Object(_) | ExprValue::Array(_) => {
412 let json = evaluate_expr_value(value, ctx)?;
414 Ok(json.to_string())
415 }
416 }
417}
418
419pub fn value_to_json(value: &Value) -> serde_json::Value {
421 match value {
422 Value::Bytes(b) => {
423 serde_json::Value::String(Base64::encode_string(b))
425 }
426 Value::String(s) => serde_json::Value::String(s.clone()),
427 Value::Integer(i) => serde_json::Value::Number((*i).into()),
428 Value::Boolean(b) => serde_json::Value::Bool(*b),
429 Value::Null => serde_json::Value::Null,
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436 use crate::actions::ArtifactValue;
437 use rite_model::{MaterialId, RoleId};
438 use std::collections::HashMap;
439
440 fn empty_context() -> HandlerContext<'static> {
441 static EMPTY_PARAMS: std::sync::LazyLock<HashMap<ParamId, serde_json::Value>> =
442 std::sync::LazyLock::new(HashMap::new);
443 static EMPTY_ARTIFACTS: std::sync::LazyLock<HashMap<ArtifactId, ArtifactValue>> =
444 std::sync::LazyLock::new(HashMap::new);
445 static EMPTY_ROLES: std::sync::LazyLock<HashMap<RoleId, String>> =
446 std::sync::LazyLock::new(HashMap::new);
447 static EMPTY_MATERIALS: std::sync::LazyLock<HashMap<MaterialId, String>> =
448 std::sync::LazyLock::new(HashMap::new);
449 HandlerContext {
450 params: &EMPTY_PARAMS,
451 artifacts: &EMPTY_ARTIFACTS,
452 roles: &EMPTY_ROLES,
453 materials: &EMPTY_MATERIALS,
454 }
455 }
456
457 fn make_context(
460 params: HashMap<ParamId, serde_json::Value>,
461 artifacts: HashMap<ArtifactId, ArtifactValue>,
462 ) -> HandlerContext<'static> {
463 static EMPTY_ROLES: std::sync::LazyLock<HashMap<RoleId, String>> =
464 std::sync::LazyLock::new(HashMap::new);
465 static EMPTY_MATERIALS: std::sync::LazyLock<HashMap<MaterialId, String>> =
466 std::sync::LazyLock::new(HashMap::new);
467 let params_box = Box::leak(Box::new(params));
468 let artifacts_box = Box::leak(Box::new(artifacts));
469 HandlerContext {
470 params: params_box,
471 artifacts: artifacts_box,
472 roles: &EMPTY_ROLES,
473 materials: &EMPTY_MATERIALS,
474 }
475 }
476
477 #[test]
478 fn test_sha256_of_string() {
479 let input = Value::String("hello".to_string());
480 let result = apply_sha256(&input).unwrap();
481
482 if let Value::Bytes(bytes) = result {
483 assert_eq!(bytes.len(), 32); let hex = hex_encode(&bytes);
485 assert_eq!(
486 hex,
487 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
488 );
489 } else {
490 panic!("Expected Bytes");
491 }
492 }
493
494 #[test]
495 fn test_sha256_of_bytes() {
496 let input = Value::Bytes(b"hello".to_vec());
497 let result = apply_sha256(&input).unwrap();
498
499 if let Value::Bytes(bytes) = result {
500 assert_eq!(bytes.len(), 32);
501 } else {
502 panic!("Expected Bytes");
503 }
504 }
505
506 #[test]
507 fn test_hex_encoding() {
508 let input = Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef]);
509 let result = apply_hex(&input).unwrap();
510
511 assert_eq!(result, Value::String("deadbeef".to_string()));
512 }
513
514 #[test]
515 fn test_base32_encoding() {
516 let input = Value::Bytes(b"hello".to_vec());
517 let result = apply_base32(&input).unwrap();
518
519 assert_eq!(result, Value::String("NBSWY3DP".to_string()));
521 }
522
523 #[test]
524 fn test_base64_encoding() {
525 let input = Value::Bytes(b"hello".to_vec());
526 let result = apply_base64(&input).unwrap();
527
528 assert_eq!(result, Value::String("aGVsbG8=".to_string()));
529 }
530
531 #[test]
532 fn test_upper() {
533 let input = Value::String("hello".to_string());
534 let result = apply_upper(&input).unwrap();
535
536 assert_eq!(result, Value::String("HELLO".to_string()));
537 }
538
539 #[test]
540 fn test_lower() {
541 let input = Value::String("HELLO".to_string());
542 let result = apply_lower(&input).unwrap();
543
544 assert_eq!(result, Value::String("hello".to_string()));
545 }
546
547 #[test]
548 fn test_pipeline_sha256_hex() {
549 let ctx = empty_context();
550
551 let expr = Expression::Pipeline {
553 source: Box::new(Expression::Literal(Literal::String("hello".to_string()))),
554 stages: vec![PipeStage::new("sha256"), PipeStage::new("hex")],
555 };
556
557 let result = evaluate(&expr, &ctx).unwrap();
558
559 if let Value::String(s) = result {
560 assert_eq!(
561 s,
562 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
563 );
564 } else {
565 panic!("Expected String");
566 }
567 }
568
569 #[test]
570 fn test_pipeline_sha256_hex_upper() {
571 let ctx = empty_context();
572
573 let expr = Expression::Pipeline {
575 source: Box::new(Expression::Literal(Literal::String("hello".to_string()))),
576 stages: vec![
577 PipeStage::new("sha256"),
578 PipeStage::new("hex"),
579 PipeStage::new("upper"),
580 ],
581 };
582
583 let result = evaluate(&expr, &ctx).unwrap();
584
585 if let Value::String(s) = result {
586 assert_eq!(
587 s,
588 "2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824"
589 );
590 } else {
591 panic!("Expected String");
592 }
593 }
594
595 #[test]
596 fn test_type_error_sha256_on_integer() {
597 let input = Value::Integer(42);
598 let result = apply_sha256(&input);
599
600 assert!(result.is_err());
601 }
602
603 #[test]
604 fn test_type_error_upper_on_bytes() {
605 let input = Value::Bytes(vec![1, 2, 3]);
606 let result = apply_upper(&input);
607
608 assert!(result.is_err());
609 }
610
611 #[test]
612 fn test_substr_via_pipeline() {
613 let ctx = empty_context();
614
615 let expr = Expression::Pipeline {
617 source: Box::new(Expression::Literal(Literal::String("abcdefgh".to_string()))),
618 stages: vec![PipeStage {
619 function: "substr".to_string(),
620 args: vec![
621 Expression::Literal(Literal::Integer(0)),
622 Expression::Literal(Literal::Integer(4)),
623 ],
624 }],
625 };
626
627 let result = evaluate(&expr, &ctx).unwrap();
628 assert_eq!(result, Value::String("abcd".to_string()));
629 }
630
631 #[test]
632 fn test_substr_from_middle_via_pipeline() {
633 let ctx = empty_context();
634
635 let expr = Expression::Pipeline {
637 source: Box::new(Expression::Literal(Literal::String("abcdefgh".to_string()))),
638 stages: vec![PipeStage {
639 function: "substr".to_string(),
640 args: vec![
641 Expression::Literal(Literal::Integer(2)),
642 Expression::Literal(Literal::Integer(4)),
643 ],
644 }],
645 };
646
647 let result = evaluate(&expr, &ctx).unwrap();
648 assert_eq!(result, Value::String("cdef".to_string()));
649 }
650
651 #[test]
652 fn test_artifact_evaluation() {
653 let mut params = HashMap::new();
654 params.insert(ParamId::new("name"), serde_json::json!("test"));
655
656 let mut artifacts = HashMap::new();
657 artifacts.insert(
659 ArtifactId::new("ksr"),
660 ArtifactValue::Bytes(b"KSR content".to_vec()),
661 );
662
663 let ctx = make_context(params, artifacts);
664
665 let expr =
667 rite_model::expression::parse_expression("${artifact.ksr | sha256 | hex}").unwrap();
668 let result = evaluate(&expr, &ctx).unwrap();
669
670 if let Value::String(s) = result {
671 assert_eq!(s.len(), 64); } else {
673 panic!("Expected String");
674 }
675 }
676
677 #[test]
678 fn test_param_evaluation() {
679 let mut params = HashMap::new();
680 params.insert(ParamId::new("expected_hash"), serde_json::json!("abc123"));
681
682 let artifacts = HashMap::new();
683 let ctx = make_context(params, artifacts);
684
685 let expr = rite_model::expression::parse_expression("${param.expected_hash}").unwrap();
686 let result = evaluate(&expr, &ctx).unwrap();
687
688 assert_eq!(result, Value::String("abc123".to_string()));
689 }
690
691 #[test]
692 fn test_sha384() {
693 let input = Value::String("hello".to_string());
694 let result = apply_sha384(&input).unwrap();
695
696 if let Value::Bytes(bytes) = result {
697 assert_eq!(bytes.len(), 48); } else {
699 panic!("Expected Bytes");
700 }
701 }
702
703 #[test]
704 fn test_sha512() {
705 let input = Value::String("hello".to_string());
706 let result = apply_sha512(&input).unwrap();
707
708 if let Value::Bytes(bytes) = result {
709 assert_eq!(bytes.len(), 64); } else {
711 panic!("Expected Bytes");
712 }
713 }
714
715 #[test]
716 fn test_concat_through_pipeline() {
717 let ctx = empty_context();
718
719 let expr = Expression::Pipeline {
722 source: Box::new(Expression::Literal(Literal::String("ignored".to_string()))),
723 stages: vec![PipeStage {
724 function: "concat".to_string(),
725 args: vec![
726 Expression::Literal(Literal::String("hello".to_string())),
727 Expression::Literal(Literal::String(" ".to_string())),
728 Expression::Literal(Literal::String("world".to_string())),
729 ],
730 }],
731 };
732
733 let result = evaluate(&expr, &ctx).unwrap();
734
735 if let Value::Bytes(b) = result {
737 assert_eq!(b, b"hello world".to_vec());
738 } else {
739 panic!("Expected Bytes, got {result:?}");
740 }
741 }
742
743 #[test]
744 fn test_parse_and_evaluate_pipeline() {
745 let ctx = empty_context();
746
747 let expr = rite_model::expression::parse_expression("${\"test\" | sha256 | hex}").unwrap();
749 let result = evaluate(&expr, &ctx).unwrap();
750
751 if let Value::String(s) = result {
752 assert_eq!(
754 s,
755 "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
756 );
757 } else {
758 panic!("Expected String");
759 }
760 }
761
762 mod ceremony_examples {
763 use super::*;
764
765 fn ceremony_context() -> HandlerContext<'static> {
766 let params: HashMap<ParamId, serde_json::Value> = [
767 ("ceremony_id", serde_json::json!("KC-2024-Q4")),
768 ("hsm_serial", serde_json::json!("HSM-001")),
769 ("key_label", serde_json::json!("MASTER-KEY")),
770 ("expected_ksr_hash", serde_json::json!("a3b8f4e91c2d6078")),
771 ("zone_name", serde_json::json!(".")),
772 ("signing_validity_days", serde_json::json!(21)),
773 ]
774 .into_iter()
775 .map(|(k, v)| (ParamId::new(k), v))
776 .collect();
777
778 let mut artifacts: HashMap<ArtifactId, ArtifactValue> = HashMap::new();
779 artifacts.insert(
780 ArtifactId::new("ksr"),
781 ArtifactValue::Bytes(b"KSR test content".to_vec()),
782 );
783 artifacts.insert(
784 ArtifactId::new("master_public_key"),
785 ArtifactValue::Bytes(b"PUBLIC KEY DATA".to_vec()),
786 );
787
788 make_context(params, artifacts)
789 }
790
791 #[test]
793 fn dnssec_hash_verification_full_pipeline() {
794 let ctx = ceremony_context();
795
796 let expr = rite_model::expression::parse_expression(
798 "${artifact.ksr | sha256 | hex | substr(0, 16)}",
799 )
800 .unwrap();
801 let result = evaluate(&expr, &ctx).unwrap();
802
803 if let Value::String(s) = result {
804 assert_eq!(s.len(), 16); assert!(s.chars().all(|c| c.is_ascii_hexdigit()));
806 } else {
807 panic!("Expected String");
808 }
809 }
810
811 #[test]
812 fn dnssec_param_comparison() {
813 let ctx = ceremony_context();
814
815 let expr =
817 rite_model::expression::parse_expression("${param.expected_ksr_hash}").unwrap();
818 let result = evaluate(&expr, &ctx).unwrap();
819
820 assert_eq!(result, Value::String("a3b8f4e91c2d6078".to_string()));
821 }
822
823 #[test]
824 fn hsm_fingerprint_computation() {
825 let ctx = ceremony_context();
826
827 let expr = rite_model::expression::parse_expression(
829 "${artifact.master_public_key | sha256 | hex}",
830 )
831 .unwrap();
832 let result = evaluate(&expr, &ctx).unwrap();
833
834 if let Value::String(s) = result {
835 assert_eq!(s.len(), 64); } else {
837 panic!("Expected String");
838 }
839 }
840 }
841}