Skip to main content

sigstore_types/
intoto.rs

1//! In-toto attestation types
2//!
3//! In-toto provides a framework for securing software supply chain integrity.
4//! This module defines types for in-toto attestation statements, commonly used
5//! with DSSE envelopes in Sigstore.
6//!
7//! Specification: <https://github.com/in-toto/attestation/blob/main/spec/v1/statement.md>
8
9use serde::{Deserialize, Serialize};
10
11/// In-toto Statement v1
12///
13/// An in-toto statement is a generic attestation format that binds a predicate
14/// to a set of subjects (artifacts). It's commonly used for SLSA provenance,
15/// vulnerability scans, and other supply chain metadata.
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct Statement {
19    /// Type identifier for the statement (typically "<https://in-toto.io/Statement/v1>")
20    #[serde(rename = "_type")]
21    pub type_: String,
22    /// Subjects (artifacts) being attested about
23    pub subject: Vec<Subject>,
24    /// Type of the predicate (e.g., "<https://slsa.dev/provenance/v1>")
25    pub predicate_type: String,
26    /// The actual attestation content (format depends on predicate_type)
27    pub predicate: serde_json::Value,
28}
29
30/// Subject of an in-toto statement
31///
32/// A subject represents an artifact being attested about, identified by
33/// its name and cryptographic digest(s).
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub struct Subject {
36    /// Name of the artifact (e.g., file name, package name)
37    pub name: String,
38    /// Cryptographic digest(s) of the artifact
39    pub digest: Digest,
40}
41
42/// Digest for a subject
43///
44/// Contains one or more cryptographic hashes of the artifact.
45/// At minimum, sha256 should be provided.
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47pub struct Digest {
48    /// SHA-256 hash (hex-encoded)
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub sha256: Option<String>,
51    /// SHA-512 hash (hex-encoded)
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub sha512: Option<String>,
54}
55
56impl Statement {
57    /// Check if any subject in the statement matches the given SHA-256 hash
58    pub fn matches_sha256(&self, hash_hex: &str) -> bool {
59        self.subject.iter().any(|subject| {
60            subject
61                .digest
62                .sha256
63                .as_ref()
64                .is_some_and(|h| h == hash_hex)
65        })
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_statement_deserialization() {
75        let json = r#"{
76            "_type": "https://in-toto.io/Statement/v1",
77            "subject": [
78                {
79                    "name": "example.txt",
80                    "digest": {
81                        "sha256": "abc123"
82                    }
83                }
84            ],
85            "predicateType": "https://slsa.dev/provenance/v1",
86            "predicate": {}
87        }"#;
88
89        let statement: Statement = serde_json::from_str(json).unwrap();
90        assert_eq!(statement.type_, "https://in-toto.io/Statement/v1");
91        assert_eq!(statement.subject.len(), 1);
92        assert_eq!(statement.subject[0].name, "example.txt");
93        assert_eq!(
94            statement.subject[0].digest.sha256,
95            Some("abc123".to_string())
96        );
97    }
98
99    #[test]
100    fn test_matches_sha256() {
101        let statement = Statement {
102            type_: "https://in-toto.io/Statement/v1".to_string(),
103            subject: vec![
104                Subject {
105                    name: "file1.txt".to_string(),
106                    digest: Digest {
107                        sha256: Some("hash1".to_string()),
108                        sha512: None,
109                    },
110                },
111                Subject {
112                    name: "file2.txt".to_string(),
113                    digest: Digest {
114                        sha256: Some("hash2".to_string()),
115                        sha512: None,
116                    },
117                },
118            ],
119            predicate_type: "https://slsa.dev/provenance/v1".to_string(),
120            predicate: serde_json::json!({}),
121        };
122
123        assert!(statement.matches_sha256("hash1"));
124        assert!(statement.matches_sha256("hash2"));
125        assert!(!statement.matches_sha256("hash3"));
126    }
127}