use crate::{Code, ServerError};
use mkit_core::hash::{hash, to_hex};
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct ReceiptEntry {
key_id: String,
alg: String,
public_key: String,
not_before_ms: Option<String>,
not_after_ms: Option<String>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct List {
version: u32,
keys: Vec<ReceiptEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicationConfig {
pub public_key: [u8; 32],
pub key_id: String,
pub key_list: String,
keys: Vec<[u8; 32]>,
}
impl PublicationConfig {
pub fn parse(seed_hex: &str, list: &str) -> Result<Self, ServerError> {
let invalid = || {
ServerError::new(
Code::InvalidArgument,
"invalid receipt-and-notice key configuration",
)
};
let seed =
zeroize::Zeroizing::new(crate::admin::auth::hex::<32>(seed_hex).ok_or_else(invalid)?);
let signing_key = ed25519_dalek::SigningKey::from_bytes(&seed);
let public_key = signing_key.verifying_key().to_bytes();
let key_id = to_hex(&hash(&public_key));
if list.len() > 65_536 {
return Err(invalid());
}
let parsed: List = serde_json::from_str(list).map_err(|_| invalid())?;
if parsed.version != 1 || parsed.keys.is_empty() || parsed.keys.len() > 128 {
return Err(invalid());
}
let mut key_ids = std::collections::BTreeSet::new();
let mut present = false;
let mut keys = Vec::new();
for entry in parsed.keys {
let public = crate::admin::auth::hex::<32>(&entry.public_key).ok_or_else(invalid)?;
ed25519_dalek::VerifyingKey::from_bytes(&public).map_err(|_| invalid())?;
if entry.alg != "ed25519"
|| entry.key_id != to_hex(&hash(&public))
|| !key_ids.insert(entry.key_id.clone())
{
return Err(invalid());
}
let bound = |value: Option<String>| -> Result<Option<i64>, ServerError> {
value
.map(|s| {
s.parse::<i64>()
.ok()
.filter(|v| v.to_string() == s)
.ok_or_else(invalid)
})
.transpose()
};
let before = bound(entry.not_before_ms)?;
let after = bound(entry.not_after_ms)?;
if before.zip(after).is_some_and(|(b, a)| b >= a) {
return Err(invalid());
}
present |= entry.key_id == key_id;
keys.push(public);
}
if !present {
return Err(invalid());
}
Ok(Self {
public_key,
key_id,
key_list: list.into(),
keys,
})
}
#[must_use]
pub fn public_keys(&self) -> &[[u8; 32]] {
&self.keys
}
}