1use std::collections::BTreeMap;
5
6use crate::error::{err, index, member, Error, ErrorCode, Result};
7use crate::json::{self, Value};
8use crate::lexical::{self, limits};
9use crate::licence::{declarations_member, Declarations};
10use crate::schema;
11
12#[derive(Clone, Debug, PartialEq, Eq, Hash)]
14pub struct Key {
15 pub id: String,
17 pub public: [u8; 32],
20 pub valid_from: String,
22 pub valid_to: Option<String>,
24}
25
26impl Key {
27 pub fn usable_at(&self, issued: &str) -> bool {
29 self.valid_from.as_str() <= issued && self.valid_to.as_deref().is_none_or(|to| issued < to)
30 }
31}
32
33#[derive(Clone, Debug, PartialEq, Eq, Hash)]
35pub struct KeyDoc {
36 pub host: String,
38 pub keys: Vec<Key>,
40}
41
42impl KeyDoc {
43 pub fn from_json(host: &str, body: &[u8]) -> Result<KeyDoc> {
47 KeyDocument::from_json(host, body).map(|d| d.keys)
48 }
49
50 pub fn key(&self, id: &str) -> Option<&Key> {
52 self.keys.iter().find(|k| k.id == id)
53 }
54
55 pub fn key_ids(&self) -> Vec<&str> {
57 self.keys.iter().map(|k| k.id.as_str()).collect()
58 }
59}
60
61#[derive(Clone, Debug, PartialEq, Eq, Hash)]
67#[non_exhaustive]
68pub struct KeyDocument {
69 pub keys: KeyDoc,
71 pub declarations: Option<Declarations>,
74}
75
76impl KeyDocument {
77 pub fn from_json(host: &str, body: &[u8]) -> Result<KeyDocument> {
81 let v = json::parse(body).map_err(|e| e.recode(ErrorCode::KeyDoc))?;
82 key_doc(host, &v)
83 }
84
85 pub fn new(keys: KeyDoc, declarations: Option<Declarations>) -> Result<KeyDocument> {
88 if let Some(d) = &declarations {
89 d.check_host(&keys.host, "$.declarations")?;
90 }
91 Ok(KeyDocument { keys, declarations })
92 }
93
94 pub fn host(&self) -> &str {
96 &self.keys.host
97 }
98}
99
100impl From<KeyDoc> for KeyDocument {
101 fn from(keys: KeyDoc) -> KeyDocument {
102 KeyDocument { keys, declarations: None }
103 }
104}
105
106pub(crate) fn key_doc(host: &str, v: &Value) -> Result<KeyDocument> {
107 let kd = ErrorCode::KeyDoc;
108 let m = schema::object_or(v, "$", kd, "a key document")?;
109 schema::exact(m, &["host", "keys", "proto"], &["declarations"], "$", kd)?;
110 let doc_host = schema::string_or(m, "host", "$", kd)?;
111 let proto = schema::string_or(m, "proto", "$", kd)?;
112 if proto != "datumesh/1" {
113 return err(kd, "$.proto", "proto must be datumesh/1");
114 }
115 if lexical::check_dns_name(host, "$").is_err() || doc_host != host {
116 return err(kd, "$.host", format!("the document names {doc_host:?}, fetched from {host:?}"));
117 }
118 let items = match schema::get(m, "keys") {
119 Value::Array(a) if !a.is_empty() && a.len() <= limits::KEYS => a,
120 _ => return err(kd, "$.keys", "keys must be an array of 1 to 64 keys"),
121 };
122 let mut keys: Vec<Key> = Vec::with_capacity(items.len());
123 for (i, item) in items.iter().enumerate() {
124 let p = index("$.keys", i);
125 let km = schema::object_or(item, &p, kd, "a key")?;
126 schema::exact(km, &["alg", "id", "public", "valid_from"], &["valid_to"], &p, kd)?;
127 let alg = schema::string_or(km, "alg", &p, kd)?;
128 let id = schema::string_or(km, "id", &p, kd)?;
129 let public = schema::string_or(km, "public", &p, kd)?;
130 let from = schema::string_or(km, "valid_from", &p, kd)?;
131 let to = match schema::opt(km, "valid_to") {
132 None => None,
133 Some(_) => Some(schema::string_or(km, "valid_to", &p, kd)?),
134 };
135 if alg != "ed25519" {
136 return err(kd, &member(&p, "alg"), "alg must be ed25519");
137 }
138 let Some(raw) = lexical::decode_hex_lower::<32>(public) else {
139 return err(kd, &member(&p, "public"), "public must be 64 lowercase hex digits");
140 };
141 if lexical::check_hex(id, 16, "").is_err() || crate::crypto::key_id(&raw) != id {
142 return err(kd, &member(&p, "id"), "id is not the key id of public");
143 }
144 if !lexical::is_instant(from) {
145 return err(kd, &member(&p, "valid_from"), "valid_from must be an instant");
146 }
147 if let Some(t) = to {
148 if !lexical::is_instant(t) {
149 return err(kd, &member(&p, "valid_to"), "valid_to must be an instant");
150 }
151 if t <= from {
152 return err(kd, &member(&p, "valid_to"), "valid_to must be after valid_from");
153 }
154 }
155 if keys.iter().any(|k| k.id == id) {
156 return err(kd, &p, format!("key {id} appears twice"));
157 }
158 keys.push(Key { id: id.into(), public: raw, valid_from: from.into(), valid_to: to.map(String::from) });
159 }
160 let declarations = match schema::opt(m, "declarations") {
162 None => None,
163 Some(d) => Some(declarations_member(d, host)?),
164 };
165 Ok(KeyDocument { keys: KeyDoc { host: host.into(), keys }, declarations })
166}
167
168pub fn keys_from_response(host: &str, status: u16, body: Option<&[u8]>) -> Result<KeyDoc> {
176 key_document_from_response(host, status, body).map(|d| d.keys)
177}
178
179pub fn key_document_from_response(host: &str, status: u16, body: Option<&[u8]>) -> Result<KeyDocument> {
183 if status != 200 {
184 return err(ErrorCode::KeyUnavailable, "$", format!("key document request for {host} returned HTTP {status}"));
185 }
186 match body {
187 Some(b) => KeyDocument::from_json(host, b),
188 None => err(ErrorCode::KeyDoc, "$", "a 200 response without a document"),
189 }
190}
191
192#[derive(Clone, Debug, Default)]
196pub struct Keys {
197 hosts: BTreeMap<String, core::result::Result<KeyDocument, Error>>,
198}
199
200impl Keys {
201 pub fn new() -> Keys {
203 Keys::default()
204 }
205
206 pub fn insert(&mut self, doc: KeyDoc) {
208 self.hosts.insert(doc.host.clone(), Ok(doc.into()));
209 }
210
211 pub fn insert_document(&mut self, doc: KeyDocument) {
213 self.hosts.insert(doc.keys.host.clone(), Ok(doc));
214 }
215
216 pub fn insert_result(&mut self, host: &str, result: Result<KeyDoc>) {
219 self.hosts.insert(host.into(), result.map(KeyDocument::from));
220 }
221
222 pub fn insert_response(&mut self, host: &str, status: u16, body: Option<&[u8]>) {
225 self.hosts.insert(host.into(), key_document_from_response(host, status, body));
226 }
227
228 pub fn insert_unavailable(&mut self, host: &str) {
230 let e = Error::new(ErrorCode::KeyUnavailable, "$", format!("key document for {host} could not be retrieved"));
231 self.hosts.insert(host.into(), Err(e));
232 }
233
234 pub fn get(&self, host: &str) -> Result<&KeyDoc> {
236 self.get_document(host).map(|d| &d.keys)
237 }
238
239 pub fn get_document(&self, host: &str) -> Result<&KeyDocument> {
242 match self.hosts.get(host) {
243 Some(Ok(doc)) => Ok(doc),
244 Some(Err(e)) => Err(e.clone()),
245 None => err(ErrorCode::KeyUnavailable, "$", format!("no key document for {host}")),
246 }
247 }
248}