1use std::collections::BTreeMap;
13
14use bynk_project::lock::{base64_decode, base64_encode};
15use serde_json::{Value, json};
16use sha2::{Digest, Sha256};
17
18pub const PROTOCOL: u64 = 1;
20
21pub const PROTOCOL_DELEGATION: u64 = 2;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct Config {
28 pub id: String,
30 pub dl: String,
32 pub api: Option<String>,
35 pub token_auth: bool,
37 pub keys: Vec<KeyEntry>,
39 pub delegations: Vec<KeyEntry>,
42 pub revoked: Vec<KeyEntry>,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct KeyEntry {
51 pub key: String,
52 pub sig: Option<String>,
53}
54
55impl Config {
56 pub fn parse(bytes: &[u8]) -> Result<Config, String> {
58 let v: Value =
59 serde_json::from_slice(bytes).map_err(|e| format!("`config.json` isn't JSON: {e}"))?;
60 let protocol = v.get("protocol").and_then(Value::as_u64);
61 if !matches!(protocol, Some(PROTOCOL | PROTOCOL_DELEGATION)) {
62 return Err(format!(
63 "`config.json` speaks protocol {}, and this toolchain speaks {PROTOCOL} and {PROTOCOL_DELEGATION}",
64 protocol.map_or("(none)".to_string(), |p| p.to_string())
65 ));
66 }
67 let text = |k: &str| v.get(k).and_then(Value::as_str).map(str::to_owned);
68 let id = text("id").ok_or("`config.json` has no `id`")?;
69 let dl = text("dl").unwrap_or_else(|| "dl".to_string());
70 let api = text("api");
71 let token_auth = match text("auth").as_deref() {
72 None | Some("none") => false,
73 Some("token") => true,
74 Some(other) => return Err(format!("`config.json`'s `auth` is `{other}`")),
75 };
76 let entries = |field: &str, required: bool| -> Result<Vec<KeyEntry>, String> {
77 let Some(list) = v.get(field) else {
78 return if required {
79 Err(format!("`config.json` has no `{field}`"))
80 } else {
81 Ok(Vec::new())
82 };
83 };
84 list.as_array()
85 .ok_or(format!("`config.json`'s `{field}` isn't a list"))?
86 .iter()
87 .map(|k| {
88 Some(KeyEntry {
89 key: k.get("key")?.as_str()?.to_owned(),
90 sig: k.get("sig").and_then(Value::as_str).map(str::to_owned),
91 })
92 })
93 .collect::<Option<Vec<_>>>()
94 .ok_or(format!("a `config.json` `{field}` entry has no `key`"))
95 };
96 let keys = entries("keys", true)?;
97 if keys.is_empty() {
98 return Err("`config.json` lists no key".to_string());
99 }
100 let delegations = entries("delegations", false)?;
101 let revoked = entries("revoked", false)?;
102 if (!delegations.is_empty() || !revoked.is_empty()) && protocol != Some(PROTOCOL_DELEGATION)
103 {
104 return Err(format!(
105 "`config.json` lists `delegations` or `revoked`, which need `\"protocol\": {PROTOCOL_DELEGATION}`"
106 ));
107 }
108 Ok(Config {
109 id,
110 dl,
111 api,
112 token_auth,
113 keys,
114 delegations,
115 revoked,
116 })
117 }
118
119 pub fn render(&self) -> String {
121 let list = |entries: &[KeyEntry]| -> Vec<Value> {
122 entries
123 .iter()
124 .map(|k| match &k.sig {
125 Some(sig) => json!({ "key": k.key, "sig": sig }),
126 None => json!({ "key": k.key }),
127 })
128 .collect()
129 };
130 let keys = list(&self.keys);
131 let delegating = !self.delegations.is_empty() || !self.revoked.is_empty();
132 let mut v = json!({
133 "protocol": if delegating { PROTOCOL_DELEGATION } else { PROTOCOL },
134 "id": self.id,
135 "dl": self.dl,
136 "auth": if self.token_auth { "token" } else { "none" },
137 "keys": keys,
138 });
139 if let Some(api) = &self.api {
140 v["api"] = json!(api);
141 }
142 if !self.delegations.is_empty() {
143 v["delegations"] = json!(list(&self.delegations));
144 }
145 if !self.revoked.is_empty() {
146 v["revoked"] = json!(list(&self.revoked));
147 }
148 serde_json::to_string_pretty(&v).expect("JSON") + "\n"
149 }
150
151 pub fn signers_with(&self, chain: &[String], verify: Verify<'_>) -> Signers {
158 let by_chain = |payload: &[u8], sig: Option<&str>| {
159 sig.is_some_and(|sig| chain.iter().any(|k| verify(k, payload, sig)))
160 };
161 let mut out = Signers {
162 keys: chain.to_vec(),
163 ..Signers::default()
164 };
165 for r in &self.revoked {
166 if self.keys.iter().any(|k| k.key == r.key) {
167 out.ignored.push(format!(
168 "a revocation of the chain key {} (only delegates can be revoked)",
169 r.key
170 ));
171 } else if by_chain(&revocation_payload(&self.id, &r.key), r.sig.as_deref()) {
172 out.revoked.push(r.key.clone());
173 } else {
174 out.ignored.push(format!(
175 "a revocation of {} that no trusted key signed",
176 r.key
177 ));
178 }
179 }
180 for d in &self.delegations {
181 if !by_chain(&delegation_payload(&self.id, &d.key), d.sig.as_deref()) {
182 out.ignored.push(format!(
183 "a delegation to {} that no trusted key signed",
184 d.key
185 ));
186 } else if !out.revoked.contains(&d.key) && !out.keys.contains(&d.key) {
187 out.keys.push(d.key.clone());
188 }
189 }
190 out
191 }
192
193 pub fn chain_from_with(&self, trusted: &str, verify: Verify<'_>) -> Option<Vec<String>> {
196 self.follow_chain_with(trusted, verify)?;
197 let start = self
198 .keys
199 .iter()
200 .position(|k| fingerprint(&k.key).as_deref() == Some(trusted))?;
201 Some(self.keys[start..].iter().map(|k| k.key.clone()).collect())
202 }
203
204 pub fn follow_chain_with(&self, trusted: &str, verify: Verify<'_>) -> Option<&str> {
208 let start = self
209 .keys
210 .iter()
211 .position(|k| fingerprint(&k.key).as_deref() == Some(trusted))?;
212 for i in start + 1..self.keys.len() {
213 let prev = &self.keys[i - 1].key;
214 let link = &self.keys[i];
215 let sig = link.sig.as_deref()?;
216 if !verify(prev, &rotation_payload(&self.id, &link.key), sig) {
217 return None;
218 }
219 }
220 Some(self.keys.last()?.key.as_str())
221 }
222}
223
224pub fn rotation_payload(registry: &str, key: &str) -> Vec<u8> {
226 canonical(&json!({ "key": key, "registry": registry }))
227}
228
229pub fn delegation_payload(registry: &str, key: &str) -> Vec<u8> {
231 canonical(&json!({ "delegate": key, "registry": registry }))
232}
233
234pub fn revocation_payload(registry: &str, key: &str) -> Vec<u8> {
236 canonical(&json!({ "registry": registry, "revoke": key }))
237}
238
239#[derive(Debug, Clone, Default, PartialEq, Eq)]
241pub struct Signers {
242 pub keys: Vec<String>,
244 pub revoked: Vec<String>,
246 pub ignored: Vec<String>,
248}
249
250pub use crate::admit::Requirement;
252
253pub type Verify<'v> = &'v dyn Fn(&str, &[u8], &str) -> bool;
256
257#[derive(Debug, Clone, PartialEq, Eq)]
259pub struct IndexLine {
260 pub name: String,
261 pub version: String,
262 pub checksum: String,
264 pub deps: Vec<Requirement>,
265 pub npm: Vec<Requirement>,
266 pub toolchain: Option<String>,
268 pub yanked: bool,
269 pub withdrawn: Option<String>,
273 pub sig: String,
274}
275
276pub const WITHDRAWN_MAX: usize = 500;
278
279impl IndexLine {
280 pub fn payload(&self, registry: &str) -> Vec<u8> {
284 let reqs = |rs: &[Requirement]| -> Value {
285 Value::Array(
286 rs.iter()
287 .map(|r| json!({ "package": r.package, "range": r.range }))
288 .collect(),
289 )
290 };
291 let mut obj = BTreeMap::new();
292 obj.insert("checksum", json!(self.checksum));
293 obj.insert("deps", reqs(&self.deps));
294 obj.insert("name", json!(self.name));
295 obj.insert("npm", reqs(&self.npm));
296 obj.insert("registry", json!(registry));
297 obj.insert("toolchain", json!(self.toolchain));
298 obj.insert("version", json!(self.version));
299 canonical(&serde_json::to_value(obj).expect("JSON"))
300 }
301
302 pub fn render(&self) -> String {
304 let reqs = |rs: &[Requirement]| -> Vec<Value> {
305 rs.iter()
306 .map(|r| json!({ "package": r.package, "range": r.range }))
307 .collect()
308 };
309 let mut v = json!({
310 "name": self.name,
311 "version": self.version,
312 "checksum": self.checksum,
313 "deps": reqs(&self.deps),
314 "npm": reqs(&self.npm),
315 "toolchain": self.toolchain,
316 "yanked": self.yanked,
317 "sig": self.sig,
318 });
319 if let Some(reason) = &self.withdrawn {
320 v["withdrawn"] = json!(reason);
321 }
322 serde_json::to_string(&v).expect("JSON")
323 }
324
325 pub fn parse(line: &str) -> Result<IndexLine, String> {
327 let v: Value =
328 serde_json::from_str(line).map_err(|e| format!("an index line isn't JSON: {e}"))?;
329 let text = |k: &str| {
330 v.get(k)
331 .and_then(Value::as_str)
332 .map(str::to_owned)
333 .ok_or(format!("an index line has no `{k}`"))
334 };
335 let reqs = |k: &str| -> Result<Vec<Requirement>, String> {
336 match v.get(k) {
337 None | Some(Value::Null) => Ok(Vec::new()),
338 Some(Value::Array(items)) => items
339 .iter()
340 .map(|r| {
341 Some(Requirement {
342 package: r.get("package")?.as_str()?.to_owned(),
343 range: r.get("range")?.as_str()?.to_owned(),
344 })
345 })
346 .collect::<Option<Vec<_>>>()
347 .ok_or(format!("an index line's `{k}` is malformed")),
348 Some(_) => Err(format!("an index line's `{k}` isn't a list")),
349 }
350 };
351 let name = text("name")?;
352 let version = text("version")?;
353 let optional = |k: &str, null: bool| -> Result<Option<String>, String> {
358 match v.get(k) {
359 None => Ok(None),
360 Some(Value::Null) if null => Ok(None),
361 Some(Value::String(s)) => Ok(Some(s.clone())),
362 Some(other) => Err(format!(
363 "{name} {version}'s `{k}` is {}, not a string{}",
364 json_kind(other),
365 if null { " or `null`" } else { "" }
366 )),
367 }
368 };
369 Ok(IndexLine {
370 checksum: text("checksum")?,
371 deps: reqs("deps")?,
372 npm: reqs("npm")?,
373 toolchain: optional("toolchain", true)?,
374 yanked: v.get("yanked").and_then(Value::as_bool).unwrap_or(false),
375 withdrawn: optional("withdrawn", false)?,
376 sig: text("sig")?,
377 name,
378 version,
379 })
380 }
381}
382
383fn json_kind(v: &Value) -> &'static str {
385 match v {
386 Value::Null => "`null`",
387 Value::Bool(_) => "a boolean",
388 Value::Number(_) => "a number",
389 Value::String(_) => "a string",
390 Value::Array(_) => "a list",
391 Value::Object(_) => "an object",
392 }
393}
394
395pub fn canonical(value: &Value) -> Vec<u8> {
399 fn sort(v: &Value) -> Value {
400 match v {
401 Value::Object(map) => {
402 let sorted: BTreeMap<&String, Value> =
403 map.iter().map(|(k, v)| (k, sort(v))).collect();
404 let mut out = serde_json::Map::new();
405 for (k, v) in sorted {
406 out.insert(k.clone(), v);
407 }
408 Value::Object(out)
409 }
410 Value::Array(items) => Value::Array(items.iter().map(sort).collect()),
411 other => other.clone(),
412 }
413 }
414 serde_json::to_vec(&sort(value)).expect("JSON")
415}
416
417pub fn key_bytes(key: &str) -> Option<Vec<u8>> {
419 let bytes = base64_decode(key.strip_prefix("ed25519:")?)?;
420 (bytes.len() == 32).then_some(bytes)
421}
422
423pub fn fingerprint(key: &str) -> Option<String> {
425 let bytes = key_bytes(key)?;
426 let digest = Sha256::digest(&bytes);
427 Some(format!("ed25519-fp:{}", base64_encode(&digest)))
428}