in_toto/models/layout/
step.rs

1//! in-toto layout's Step
2
3use std::str::FromStr;
4
5use serde::ser::Serializer;
6use serde::{Deserialize, Serialize};
7
8use crate::crypto::KeyId;
9use crate::{supply_chain_item_derive, Error, Result};
10
11use super::rule::ArtifactRule;
12use super::supply_chain_item::SupplyChainItem;
13
14/// Wrapper type for a command in step.
15#[derive(
16    Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Deserialize,
17)]
18pub struct Command(Vec<String>);
19
20impl Command {
21    pub fn is_empty(&self) -> bool {
22        self.0.is_empty()
23    }
24}
25
26impl AsRef<[String]> for Command {
27    fn as_ref(&self) -> &[String] {
28        &self.0
29    }
30}
31
32impl From<String> for Command {
33    fn from(str: String) -> Self {
34        let paras: Vec<String> = str
35            .split_whitespace()
36            .collect::<Vec<&str>>()
37            .iter()
38            .map(|s| s.to_string())
39            .collect();
40        Command(paras)
41    }
42}
43
44impl From<Vec<String>> for Command {
45    fn from(strs: Vec<String>) -> Self {
46        Command::from(&strs[..])
47    }
48}
49
50impl From<&[String]> for Command {
51    fn from(strs: &[String]) -> Self {
52        Command(strs.to_vec())
53    }
54}
55
56impl From<&str> for Command {
57    fn from(str: &str) -> Self {
58        let paras: Vec<String> = str
59            .split_whitespace()
60            .collect::<Vec<&str>>()
61            .iter()
62            .map(|s| s.to_string())
63            .collect();
64        Command(paras)
65    }
66}
67
68impl FromStr for Command {
69    type Err = Error;
70
71    /// Parse a Command from a string.
72    fn from_str(string: &str) -> Result<Self> {
73        let paras: Vec<String> = string
74            .split_whitespace()
75            .collect::<Vec<&str>>()
76            .iter()
77            .map(|s| s.to_string())
78            .collect();
79        Ok(Command(paras))
80    }
81}
82
83impl Serialize for Command {
84    fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
85    where
86        S: Serializer,
87    {
88        self.0.serialize(ser)
89    }
90}
91
92/// Step represents an in-toto step of the supply chain performed by a functionary.
93/// During final product verification in-toto looks for corresponding Link
94/// metadata, which is used as signed evidence that the step was performed
95/// according to the supply chain definition.
96/// Materials and products used/produced by the step are constrained by the
97/// artifact rules in the step's supply_chain_item's expected_materials and
98/// expected_products fields.
99#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
100pub struct Step {
101    #[serde(rename = "_type")]
102    pub typ: String,
103    pub threshold: u32,
104    pub name: String,
105    pub expected_materials: Vec<ArtifactRule>,
106    pub expected_products: Vec<ArtifactRule>,
107    #[serde(rename = "pubkeys")]
108    pub pub_keys: Vec<KeyId>,
109    pub expected_command: Command,
110}
111
112impl Step {
113    pub fn new(name: &str) -> Self {
114        Step {
115            pub_keys: Vec::new(),
116            expected_command: Command::default(),
117            threshold: 0,
118            name: name.into(),
119            expected_materials: Vec::new(),
120            expected_products: Vec::new(),
121            typ: "step".into(),
122        }
123    }
124
125    /// Add a pub key for this Step
126    pub fn add_key(mut self, key: KeyId) -> Self {
127        self.pub_keys.push(key);
128        self
129    }
130
131    /// Set expected command for this Step
132    pub fn expected_command(mut self, command: Command) -> Self {
133        self.expected_command = command;
134        self
135    }
136
137    /// Set threshold for this Step
138    pub fn threshold(mut self, threshold: u32) -> Self {
139        self.threshold = threshold;
140        self
141    }
142
143    // Derive operations on `materials`/`products` and `name`
144    supply_chain_item_derive!();
145}
146
147impl SupplyChainItem for Step {
148    fn name(&self) -> &str {
149        &self.name
150    }
151
152    fn expected_materials(&self) -> &Vec<ArtifactRule> {
153        &self.expected_materials
154    }
155
156    fn expected_products(&self) -> &Vec<ArtifactRule> {
157        &self.expected_products
158    }
159}
160
161#[cfg(test)]
162mod test {
163    use std::str::FromStr;
164
165    use serde_json::json;
166
167    use crate::{
168        crypto::KeyId,
169        models::rule::{Artifact, ArtifactRule},
170        Result,
171    };
172
173    use super::Step;
174
175    #[test]
176    fn serialize_step() -> Result<()> {
177        let step = Step::new("package")
178            .add_expected_material(ArtifactRule::Match {
179                pattern: "foo.py".into(),
180                in_src: None,
181                with: Artifact::Products,
182                in_dst: None,
183                from: "write-code".into(),
184            })
185            .add_expected_product(ArtifactRule::Create("foo.tar.gz".into()))
186            .expected_command("tar zcvf foo.tar.gz foo.py".into())
187            .add_key(KeyId::from_str(
188                "70ca5750c2eda80b18f41f4ec5f92146789b5d68dd09577be422a0159bd13680",
189            )?)
190            .threshold(1);
191
192        let json_serialize = serde_json::to_value(&step)?;
193        let json = json!(
194        {
195            "_type": "step",
196            "name": "package",
197            "expected_materials": [
198               ["MATCH", "foo.py", "WITH", "PRODUCTS", "FROM", "write-code"]
199            ],
200            "expected_products": [
201               ["CREATE", "foo.tar.gz"]
202            ],
203            "expected_command": ["tar", "zcvf", "foo.tar.gz", "foo.py"],
204            "pubkeys": [
205               "70ca5750c2eda80b18f41f4ec5f92146789b5d68dd09577be422a0159bd13680"
206            ],
207            "threshold": 1
208          }
209        );
210        assert_eq!(
211            json, json_serialize,
212            "{:#?} != {:#?}",
213            json, json_serialize
214        );
215        Ok(())
216    }
217
218    #[test]
219    fn deserialize_step() -> Result<()> {
220        let json = r#"
221        {
222            "_type": "step",
223            "name": "package",
224            "expected_materials": [
225               ["MATCH", "foo.py", "WITH", "PRODUCTS", "FROM", "write-code"]
226            ],
227            "expected_products": [
228               ["CREATE", "foo.tar.gz"]
229            ],
230            "expected_command": ["tar", "zcvf", "foo.tar.gz", "foo.py"],
231            "pubkeys": [
232               "70ca5750c2eda80b18f41f4ec5f92146789b5d68dd09577be422a0159bd13680"
233            ],
234            "threshold": 1
235          }"#;
236        let step_parsed: Step = serde_json::from_str(json)?;
237        let step = Step::new("package")
238            .add_expected_material(ArtifactRule::Match {
239                pattern: "foo.py".into(),
240                in_src: None,
241                with: Artifact::Products,
242                in_dst: None,
243                from: "write-code".into(),
244            })
245            .add_expected_product(ArtifactRule::Create("foo.tar.gz".into()))
246            .expected_command("tar zcvf foo.tar.gz foo.py".into())
247            .add_key(KeyId::from_str(
248                "70ca5750c2eda80b18f41f4ec5f92146789b5d68dd09577be422a0159bd13680",
249            )?)
250            .threshold(1);
251        assert_eq!(step_parsed, step);
252        Ok(())
253    }
254}