| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
use super::json; |
| 5 |
use super::open; |
| 6 |
use super::ui; |
| 7 |
use anyhow::{anyhow, Result}; |
| 8 |
use oxy_core::hash::KeyId; |
| 9 |
use std::path::{Path, PathBuf}; |
| 10 |
|
| 11 |
pub fn verify(hub: Option<&str>, key: Option<&str>, all: bool, as_json: bool) -> Result<i32> { |
| 12 |
let repo = open()?; |
| 13 |
let remote = repo.default_remote(); |
| 14 |
let url = match hub { |
| 15 |
Some(h) if h.contains("://") => h.to_string(), |
| 16 |
Some(h) => format!("https://{h}"), |
| 17 |
None => repo.config.remotes.get(&remote).cloned().ok_or_else(|| { |
| 18 |
anyhow!("name the hub to check (this checkout has no remote {remote})") |
| 19 |
})?, |
| 20 |
}; |
| 21 |
let ca = repo |
| 22 |
.config |
| 23 |
.remote_cas |
| 24 |
.get(&remote) |
| 25 |
.map(Path::new) |
| 26 |
.filter(|_| hub.is_none()); |
| 27 |
let audit = repo.audit_log(&url, ca).map_err(|e| anyhow!("{e}"))?; |
| 28 |
let key = match key { |
| 29 |
Some(k) => Some( |
| 30 |
KeyId::from_hex(k) |
| 31 |
.ok_or_else(|| anyhow!("--key takes a full KeyId (64 hex digits)"))?, |
| 32 |
), |
| 33 |
None => None, |
| 34 |
}; |
| 35 |
let rid = repo.id().ok(); |
| 36 |
let listed: Vec<(usize, &oxy_core::tlog::Entry)> = audit |
| 37 |
.entries |
| 38 |
.iter() |
| 39 |
.enumerate() |
| 40 |
.filter(|(_, e)| match (&key, all) { |
| 41 |
(_, true) => true, |
| 42 |
(Some(k), _) => e.signers.contains(k), |
| 43 |
(None, _) => Some(e.repo) == rid, |
| 44 |
}) |
| 45 |
.collect(); |
| 46 |
let what = match (&key, all) { |
| 47 |
(_, true) => "every entry".to_string(), |
| 48 |
(Some(k), _) => format!("entries signed by {}", k.short(12)), |
| 49 |
(None, _) => "this repository's entries".to_string(), |
| 50 |
}; |
| 51 |
if as_json { |
| 52 |
println!( |
| 53 |
"{}", |
| 54 |
json::obj([ |
| 55 |
("hub", json::s(&audit.hub)), |
| 56 |
("origin", json::s(&audit.checkpoint.origin)), |
| 57 |
("size", json::n(audit.checkpoint.size)), |
| 58 |
( |
| 59 |
"root", |
| 60 |
json::s(&oxy_core::hash::ObjectRef(audit.checkpoint.root).to_hex()) |
| 61 |
), |
| 62 |
( |
| 63 |
"entries", |
| 64 |
json::arr(listed.iter().map(|(i, e)| { |
| 65 |
json::obj([ |
| 66 |
("index", json::n(*i)), |
| 67 |
("kind", json::s(&e.kind)), |
| 68 |
( |
| 69 |
"repo", |
| 70 |
json::s( |
| 71 |
&e.repo |
| 72 |
.iter() |
| 73 |
.map(|b| format!("{b:02x}")) |
| 74 |
.collect::<String>(), |
| 75 |
), |
| 76 |
), |
| 77 |
("object", json::s(&e.object.to_hex())), |
| 78 |
( |
| 79 |
"signers", |
| 80 |
json::arr(e.signers.iter().map(|k| json::s(&k.to_hex()))), |
| 81 |
), |
| 82 |
("received_ms", json::n(e.received)), |
| 83 |
]) |
| 84 |
})), |
| 85 |
), |
| 86 |
]) |
| 87 |
); |
| 88 |
return Ok(0); |
| 89 |
} |
| 90 |
for n in &audit.notes { |
| 91 |
eprintln!("{} {n}", ui::caution()); |
| 92 |
} |
| 93 |
println!( |
| 94 |
"{} {}: {} entries make the root its log key signed, and extend what you saw before", |
| 95 |
ui::done("Verified"), |
| 96 |
ui::path(&audit.checkpoint.origin), |
| 97 |
audit.checkpoint.size |
| 98 |
); |
| 99 |
if listed.is_empty() { |
| 100 |
println!(" {} {}", ui::note(), ui::dim(format!("no {what} in it"))); |
| 101 |
return Ok(0); |
| 102 |
} |
| 103 |
println!( |
| 104 |
" {} {}", |
| 105 |
ui::note(), |
| 106 |
ui::dim(format!("{} of {what}:", listed.len())) |
| 107 |
); |
| 108 |
for (i, e) in listed { |
| 109 |
let signers: Vec<String> = e.signers.iter().map(|k| k.short(12)).collect(); |
| 110 |
println!( |
| 111 |
" #{i:<6} {:<11} {} {} {}", |
| 112 |
e.kind, |
| 113 |
ui::hash(e.object.short(12)), |
| 114 |
ui::dim(if signers.is_empty() { |
| 115 |
"unsigned".to_string() |
| 116 |
} else { |
| 117 |
format!("by {}", signers.join(", ")) |
| 118 |
}), |
| 119 |
ui::dim(ui::ago(e.received)) |
| 120 |
); |
| 121 |
} |
| 122 |
Ok(0) |
| 123 |
} |
| 124 |
|
| 125 |
|
| 126 |
pub fn witness_serve( |
| 127 |
name: &str, |
| 128 |
logs: &[String], |
| 129 |
state: PathBuf, |
| 130 |
bind: &str, |
| 131 |
tls: Option<(PathBuf, PathBuf)>, |
| 132 |
) -> Result<i32> { |
| 133 |
let home = oxy_core::repo::default_home(); |
| 134 |
let (identity, sk) = oxy_core::repo::home_key(&home) |
| 135 |
.map_err(|e| anyhow!("{e}"))? |
| 136 |
.ok_or_else(|| anyhow!("{} names no key (author.key) with its secret: a witness signs with it; oxy identity create", home.display()))?; |
| 137 |
let mut parsed = Vec::new(); |
| 138 |
for l in logs { |
| 139 |
let (origin, file) = l |
| 140 |
.split_once('=') |
| 141 |
.ok_or_else(|| anyhow!("--log takes ORIGIN=FILE, as oxygenhub.io/log=hub.identity"))?; |
| 142 |
let bytes = std::fs::read(file).map_err(|e| anyhow!("{file}: {e}"))?; |
| 143 |
let key = |
| 144 |
oxy_core::identity::Identity::decode(&bytes).map_err(|e| anyhow!("{file}: {e}"))?; |
| 145 |
parsed.push((origin.to_string(), key)); |
| 146 |
} |
| 147 |
let listener = std::net::TcpListener::bind(bind).map_err(|e| anyhow!("binding {bind}: {e}"))?; |
| 148 |
println!( |
| 149 |
"{} {} logs as {} with key {} on {}://{bind}/add-checkpoint", |
| 150 |
ui::done("Witnessing"), |
| 151 |
parsed.len(), |
| 152 |
ui::path(name), |
| 153 |
identity.hash().short(12), |
| 154 |
if tls.is_some() { "https" } else { "http" } |
| 155 |
); |
| 156 |
for (o, k) in &parsed { |
| 157 |
println!( |
| 158 |
" {} {} {}", |
| 159 |
ui::note(), |
| 160 |
o, |
| 161 |
ui::dim(format!("signed by {}", k.hash().short(12))) |
| 162 |
); |
| 163 |
} |
| 164 |
oxy_server::witness::run_blocking( |
| 165 |
listener, |
| 166 |
oxy_server::witness::WitnessConfig { |
| 167 |
name: name.to_string(), |
| 168 |
identity, |
| 169 |
sk, |
| 170 |
logs: parsed, |
| 171 |
state, |
| 172 |
tls: tls.map(|(cert, key)| oxy_server::TlsFiles { cert, key }), |
| 173 |
}, |
| 174 |
)?; |
| 175 |
Ok(0) |
| 176 |
} |