1use wsc::dsse::{DsseEnvelope, Ed25519DsseSigner, Ed25519DsseVerifier};
16
17use crate::install::{ManifestVerifier, VerifyError};
18
19pub const LAYER_PAYLOAD_TYPE: &str = "application/vnd.pulseengine.varve.layer.v1+json";
21
22pub struct PinnedKeyVerifier {
25 verifier: Ed25519DsseVerifier,
26}
27
28impl PinnedKeyVerifier {
29 pub fn from_public_key_bytes(public_key: &[u8]) -> Result<Self, VerifyError> {
30 let verifier = Ed25519DsseVerifier::from_bytes(public_key)
31 .map_err(|e| VerifyError(format!("invalid trust-root public key: {e}")))?;
32 Ok(PinnedKeyVerifier { verifier })
33 }
34}
35
36impl ManifestVerifier for PinnedKeyVerifier {
37 fn verify(&self, fetched: &[u8]) -> Result<Vec<u8>, VerifyError> {
38 verify_with(&self.verifier, fetched, LAYER_PAYLOAD_TYPE)
39 }
40}
41
42pub fn dsse_verify_typed(
48 envelope: &[u8],
49 expected_payload_type: &str,
50 root_public_key: &[u8],
51) -> Result<Vec<u8>, VerifyError> {
52 let verifier = Ed25519DsseVerifier::from_bytes(root_public_key)
53 .map_err(|e| VerifyError(format!("invalid trust-root public key: {e}")))?;
54 verify_with(&verifier, envelope, expected_payload_type)
55}
56
57fn verify_with(
58 verifier: &Ed25519DsseVerifier,
59 envelope: &[u8],
60 expected_payload_type: &str,
61) -> Result<Vec<u8>, VerifyError> {
62 let text = std::str::from_utf8(envelope)
63 .map_err(|_| VerifyError("fetched bytes are not UTF-8 DSSE JSON".into()))?;
64 let envelope = DsseEnvelope::from_json(text)
65 .map_err(|e| VerifyError(format!("not a DSSE envelope: {e}")))?;
66 if envelope.payload_type != expected_payload_type {
67 return Err(VerifyError(format!(
68 "envelope payload type is '{}', expected '{expected_payload_type}' — refusing to \
69 accept a signed document as something it is not",
70 envelope.payload_type
71 )));
72 }
73 envelope.verify_all(verifier).map_err(|e| {
74 VerifyError(format!(
75 "signature does not verify against the trust root: {e}"
76 ))
77 })
78}
79
80pub fn dsse_sign_typed(
84 payload: &[u8],
85 payload_type: &str,
86 secret_key: &[u8],
87 key_id: &str,
88) -> Result<String, VerifyError> {
89 let signer = Ed25519DsseSigner::from_bytes(secret_key, Some(key_id.to_string()))
90 .map_err(|e| VerifyError(format!("invalid signing key: {e}")))?;
91 let envelope = DsseEnvelope::sign(payload, payload_type, &signer)
92 .map_err(|e| VerifyError(format!("signing failed: {e}")))?;
93 envelope
94 .to_json_pretty()
95 .map_err(|e| VerifyError(format!("envelope serialization failed: {e}")))
96}
97
98pub fn sign_layer_manifest(
100 payload: &[u8],
101 secret_key: &[u8],
102 key_id: &str,
103) -> Result<String, VerifyError> {
104 dsse_sign_typed(payload, LAYER_PAYLOAD_TYPE, secret_key, key_id)
105}
106
107pub fn generate_root_keypair() -> (Vec<u8>, Vec<u8>) {
111 let kp = wsc::KeyPair::generate();
112 (kp.sk.sk.to_vec(), kp.pk.pk.to_vec())
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use crate::install::{InstallError, InstallPolicy, install};
119 use crate::manifest::fixtures::manifest_with_tools;
120 use crate::pin::Pin;
121 use crate::rollback::HighWaterMarks;
122 use crate::source::MemorySource;
123 use crate::store::{Store, manifest_digest};
124
125 fn keys() -> (Vec<u8>, Vec<u8>) {
126 generate_root_keypair()
127 }
128
129 #[test]
131 fn a_signed_manifest_verifies_and_yields_its_payload() {
132 let (sk, pk) = keys();
133 let payload = manifest_with_tools("2026.07.0", "qualified", 1, "2026-07-31T09:14:00Z", &[]);
134 let envelope = sign_layer_manifest(&payload, &sk, "varve-root-1").unwrap();
135 let verifier = PinnedKeyVerifier::from_public_key_bytes(&pk).unwrap();
136 use crate::install::ManifestVerifier as _;
137 assert_eq!(verifier.verify(envelope.as_bytes()).unwrap(), payload);
138 }
139
140 #[test]
142 fn a_signature_from_an_untrusted_key_is_rejected() {
143 let (sk, _) = keys();
144 let (_, other_pk) = keys();
145 let payload = manifest_with_tools("2026.07.0", "qualified", 1, "2026-07-31T09:14:00Z", &[]);
146 let envelope = sign_layer_manifest(&payload, &sk, "varve-root-1").unwrap();
147 let verifier = PinnedKeyVerifier::from_public_key_bytes(&other_pk).unwrap();
148 use crate::install::ManifestVerifier as _;
149 assert!(verifier.verify(envelope.as_bytes()).is_err());
150 }
151
152 #[test]
154 fn a_tampered_payload_is_rejected() {
155 let (sk, pk) = keys();
156 let payload = manifest_with_tools("2026.07.0", "qualified", 1, "2026-07-31T09:14:00Z", &[]);
157 let envelope = sign_layer_manifest(&payload, &sk, "varve-root-1").unwrap();
158 let mut parsed: serde_json::Value = serde_json::from_str(&envelope).unwrap();
161 let b64 = parsed["payload"].as_str().unwrap().to_string();
162 let mut chars: Vec<char> = b64.chars().collect();
163 chars[0] = if chars[0] == 'A' { 'B' } else { 'A' };
164 parsed["payload"] = serde_json::Value::String(chars.into_iter().collect());
165 let tampered = serde_json::to_string(&parsed).unwrap();
166 let verifier = PinnedKeyVerifier::from_public_key_bytes(&pk).unwrap();
167 use crate::install::ManifestVerifier as _;
168 assert!(verifier.verify(tampered.as_bytes()).is_err());
169 }
170
171 #[test]
173 fn a_signed_envelope_of_the_wrong_payload_type_is_rejected() {
174 let (sk, pk) = keys();
175 let payload = b"some signed but non-manifest content";
176 let signer = wsc::dsse::Ed25519DsseSigner::from_bytes(&sk, None).unwrap();
177 let envelope = wsc::dsse::DsseEnvelope::sign(payload, "application/other", &signer)
178 .unwrap()
179 .to_json()
180 .unwrap();
181 let verifier = PinnedKeyVerifier::from_public_key_bytes(&pk).unwrap();
182 use crate::install::ManifestVerifier as _;
183 let err = verifier.verify(envelope.as_bytes()).unwrap_err();
184 assert!(err.to_string().contains("payload type"), "got: {err}");
185 }
186
187 #[test]
189 fn install_accepts_a_properly_signed_layer_and_rejects_the_same_bytes_resigned_by_an_impostor()
190 {
191 let (sk, pk) = keys();
192 let (impostor_sk, _) = keys();
193 let synth = b"tool-bytes".to_vec();
194 let blob_digest = manifest_digest(&synth);
195 let payload = manifest_with_tools(
196 "2026.07.0",
197 "qualified",
198 1,
199 "2026-07-31T09:14:00Z",
200 &[("synth", &blob_digest)],
201 );
202 let pin = Pin::parse(
203 "manifest-version = 1\n[toolchain]\nchannel = \"qualified\"\nlayer = \"2026.07.0\"\n",
204 "varve.toml",
205 )
206 .unwrap();
207 let policy = InstallPolicy {
208 index: None,
209 now: "2026-08-07T00:00:00Z",
210 staleness_threshold_days: 90,
211 platform: "test-platform",
212 };
213 let verifier = PinnedKeyVerifier::from_public_key_bytes(&pk).unwrap();
214
215 let good = sign_layer_manifest(&payload, &sk, "varve-root-1").unwrap();
217 let source = MemorySource::new()
218 .with_manifest(good.as_bytes())
219 .with_blob(&blob_digest, &synth);
220 let tmp = tempfile::tempdir().unwrap();
221 let store = Store::at(tmp.path().join("root"));
222 let mut marks = HighWaterMarks::load(&tmp.path().join("root")).unwrap();
223 let outcome = install(&pin, &source, &verifier, &store, &mut marks, &policy).unwrap();
224 assert_eq!(outcome.digest, manifest_digest(&payload));
225 let entry = store.get(&outcome.digest).unwrap().unwrap();
226 assert_eq!(
227 std::fs::read(entry.root.join("layer.json")).unwrap(),
228 payload,
229 "the core stores the verified payload, not the envelope"
230 );
231
232 let evil = sign_layer_manifest(&payload, &impostor_sk, "varve-root-1").unwrap();
234 let evil_source = MemorySource::new()
235 .with_manifest(evil.as_bytes())
236 .with_blob(&blob_digest, &synth);
237 let tmp2 = tempfile::tempdir().unwrap();
238 let store2 = Store::at(tmp2.path().join("root"));
239 let mut marks2 = HighWaterMarks::load(&tmp2.path().join("root")).unwrap();
240 let err =
241 install(&pin, &evil_source, &verifier, &store2, &mut marks2, &policy).unwrap_err();
242 assert!(matches!(err, InstallError::Verify(_)), "got: {err}");
243 assert!(store2.list().unwrap().is_empty());
244 }
245}