sigstore_verification/sources/
file.rs1use crate::api::{Attestation, SigstoreBundle, DsseEnvelope, Signature};
2use crate::sources::{ArtifactRef, AttestationSource};
3use crate::{AttestationError, Result};
4use async_trait::async_trait;
5use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
6use std::path::{Path, PathBuf};
7use tokio::fs;
8
9pub struct FileSource {
11 attestation_path: PathBuf,
13}
14
15impl FileSource {
16 pub fn new(path: impl AsRef<Path>) -> Self {
17 Self {
18 attestation_path: path.as_ref().to_path_buf(),
19 }
20 }
21
22 pub async fn load_bundle(&self) -> Result<serde_json::Value> {
24 let content = fs::read_to_string(&self.attestation_path)
25 .await
26 .map_err(AttestationError::Io)?;
27
28 serde_json::from_str(&content)
29 .map_err(AttestationError::Json)
30 }
31
32 pub async fn load_signature(&self) -> Result<Vec<u8>> {
34 fs::read(&self.attestation_path)
35 .await
36 .map_err(AttestationError::Io)
37 }
38}
39
40#[async_trait]
41impl AttestationSource for FileSource {
42 async fn fetch_attestations(&self, _artifact: &ArtifactRef) -> Result<Vec<Attestation>> {
43 let content = fs::read_to_string(&self.attestation_path)
44 .await
45 .map_err(AttestationError::Io)?;
46
47 let mut attestations = Vec::new();
49
50 for line in content.lines() {
51 if line.trim().is_empty() {
52 continue;
53 }
54
55 if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(line) {
56 if let (Some(payload_type), Some(payload), Some(signatures)) = (
58 json_value.get("payloadType"),
59 json_value.get("payload"),
60 json_value.get("signatures")
61 ) {
62 if payload_type.as_str() == Some("application/vnd.in-toto+json") {
63 let mut parsed_signatures = Vec::new();
65 if let Some(sig_array) = signatures.as_array() {
66 for sig_obj in sig_array {
67 let sig_string = sig_obj.get("sig")
68 .and_then(|s| s.as_str())
69 .unwrap_or("")
70 .to_string();
71 let keyid = sig_obj.get("keyid")
72 .and_then(|k| k.as_str())
73 .map(|s| s.to_string());
74
75 parsed_signatures.push(Signature {
76 sig: sig_string,
77 keyid,
78 });
79 }
80 }
81
82 let bundle = SigstoreBundle {
83 media_type: "application/vnd.in-toto+json".to_string(),
84 dsse_envelope: DsseEnvelope {
85 payload: payload.as_str().unwrap_or("").to_string(),
86 payload_type: payload_type.as_str().unwrap_or("").to_string(),
87 signatures: parsed_signatures,
88 },
89 verification_material: None, };
91
92 let attestation = Attestation {
93 bundle: Some(bundle),
94 bundle_url: None,
95 };
96 attestations.push(attestation);
97 continue;
98 }
99 }
100
101 if let Some(type_field) = json_value.get("_type") {
103 let type_str = type_field.as_str().unwrap_or("");
104 if type_str.starts_with("https://in-toto.io/Statement/v") {
105 let bundle = SigstoreBundle {
107 media_type: "application/vnd.in-toto+json".to_string(),
108 dsse_envelope: DsseEnvelope {
109 payload: BASE64.encode(serde_json::to_string(&json_value)?.as_bytes()),
110 payload_type: "application/vnd.in-toto+json".to_string(),
111 signatures: vec![Signature {
112 sig: "".to_string(), keyid: None,
114 }],
115 },
116 verification_material: None,
117 };
118
119 let attestation = Attestation {
120 bundle: Some(bundle),
121 bundle_url: None,
122 };
123 attestations.push(attestation);
124 continue;
125 }
126 }
127
128 if let Ok(attestation) = serde_json::from_value::<Attestation>(json_value.clone()) {
130 attestations.push(attestation);
131 continue;
132 }
133
134 if let Ok(bundle) = serde_json::from_value::<SigstoreBundle>(json_value) {
136 let attestation = Attestation {
137 bundle: Some(bundle),
138 bundle_url: None,
139 };
140 attestations.push(attestation);
141 }
142 }
143 }
144
145 if attestations.is_empty() {
146 return Err(AttestationError::Verification(
147 "File does not contain valid attestations or SLSA provenance".into()
148 ));
149 }
150
151 Ok(attestations)
152 }
153
154 fn source_type(&self) -> &'static str {
155 "File"
156 }
157}