//! 测试 key 只用于显式注入的 sumdb 对端,不进入产品公钥或项目设置。 use super::*; use base64::{Engine, engine::general_purpose::STANDARD}; use ring::signature::{Ed25519KeyPair, KeyPair}; use sha2::{Digest, Sha256}; use std::{ cell::RefCell, io::{Cursor, Write}, }; #[derive(Default)] pub(super) struct Cache(pub RefCell>>); impl Store for Cache { fn get(&self, hash: &str, _: u64) -> Result>, String> { Ok(self.0.borrow().get(hash).cloned()) } fn put(&self, bytes: &[u8]) -> Result<(), String> { self.0.borrow_mut().insert( crate::compiler::project::dependency::digest(bytes), bytes.to_vec(), ); Ok(()) } } #[derive(Clone, Default)] pub(super) struct Registry(pub BTreeMap>); impl Transport for Registry { fn get(&self, url: &str, limit: usize) -> Result, String> { let bytes = self .0 .get(url) .ok_or_else(|| format!("missing owned registry response {url}"))?; assert!(bytes.len() <= limit); Ok(bytes.clone()) } } pub(super) fn runtime() -> Metadata { serde_json::from_value(serde_json::json!({"pack":{"name":"go", "version":"1.25.0", "sha256":"0".repeat(64)}, "target":BuildTarget::host().unwrap().name(), "python_markers":null, "python_wheel_tags":[]})).unwrap() } pub(super) fn zip(module: &str, version: &str, contents: &str) -> Vec { let mut archive = zip::ZipWriter::new(Cursor::new(Vec::new())); archive .start_file( format!("{module}@v{version}/value.go"), zip::write::SimpleFileOptions::default(), ) .unwrap(); archive.write_all(contents.as_bytes()).unwrap(); archive.finish().unwrap().into_inner() } fn key() -> Ed25519KeyPair { Ed25519KeyPair::from_seed_unchecked(&[47; 32]).unwrap() } fn key_id() -> Vec { let mut hash = Sha256::new(); hash.update(b"dever-sumdb-test\n"); hash.update([1]); hash.update(key().public_key().as_ref()); hash.finalize()[..4].to_vec() } pub(super) fn verifier() -> Verifier { let mut bytes = vec![1]; bytes.extend_from_slice(key().public_key().as_ref()); Verifier::new(&format!( "dever-sumdb-test+{}+{}", key_id() .iter() .map(|byte| format!("{byte:02x}")) .collect::(), STANDARD.encode(bytes) )) .unwrap() } fn root(hashes: &[[u8; 32]]) -> [u8; 32] { if hashes.is_empty() { return Sha256::digest([]).into(); } if hashes.len() == 1 { return hashes[0]; } let split = hashes.len().next_power_of_two() / 2; let mut hash = Sha256::new(); hash.update([1]); hash.update(root(&hashes[..split])); hash.update(root(&hashes[split..])); hash.finalize().into() } impl Registry { pub fn module(&mut self, name: &str, version: &str, source: &str) { let prefix = format!( "https://proxy.golang.org/{}/@v/v{version}", module::escape(name) ); self.0 .insert(format!("{prefix}.mod"), source.as_bytes().to_vec()); self.0.insert( format!("{prefix}.zip"), zip(name, version, "package value\n"), ); } pub fn seal(&mut self) { self.0 .retain(|path, _| !path.starts_with("https://sum.golang.org/")); let records = self .0 .iter() .filter_map(|(path, source)| { let path = path.strip_prefix("https://proxy.golang.org/")?; let (name, version) = path.split_once("/@v/v")?; let version = version.strip_suffix(".mod")?; let archive = &self.0[&format!("https://proxy.golang.org/{name}/@v/v{version}.zip")]; let text = format!( "{name} v{version} {}\n{name} v{version}/go.mod {}\n", sumdb::zip_hash(name, version, archive).unwrap(), sumdb::mod_hash(source) ); Some(( format!("https://sum.golang.org/lookup/{name}@v{version}"), text, )) }) .collect::>(); let hashes = records .iter() .map(|(_, text)| { let mut hash = Sha256::new(); hash.update([0]); hash.update(text); hash.finalize().into() }) .collect::>(); let text = format!( "go.sum database tree\n{}\n{}\n", hashes.len(), STANDARD.encode(root(&hashes)) ); let mut signature = key_id(); signature.extend_from_slice(key().sign(text.as_bytes()).as_ref()); let note = format!( "{text}\n— dever-sumdb-test {}\n", STANDARD.encode(signature) ); self.0.insert( "https://sum.golang.org/latest".into(), note.as_bytes().to_vec(), ); for (id, (path, text)) in records.into_iter().enumerate() { self.0 .insert(path, format!("{id}\n{text}\n{note}").into_bytes()); } assert!(hashes.len() < 256); self.0.insert( format!("https://sum.golang.org/tile/8/0/000.p/{}", hashes.len()), hashes.into_iter().flatten().collect(), ); } } pub(super) fn graph() -> Registry { let mut registry = Registry::default(); for (name, version, source) in [ ( "a", "1.0.0", "module example.com/a\nrequire (\nexample.com/b v1.0.0\nexample.com/c v1.0.0\n)\n", ), ("b", "1.0.0", "module example.com/b\n"), ("b", "1.2.0", "module example.com/b\n"), ( "c", "1.0.0", "module example.com/c\nrequire example.com/b v1.2.0\n", ), ] { registry.module(&format!("example.com/{name}"), version, source); } registry.seal(); registry }