use derive_deftly::Deftly;
use tor_cert::KeyUnknownCert;
use tor_llcrypto::pk::ed25519;
use crate::{
doc::routerdesc::RouterDesc,
parse2::VerifyFailed,
types::{descriptor::*, *},
};
#[derive(Debug, Clone, PartialEq, Deftly)]
#[derive_deftly(NetdocParseableUnverified, NetdocEncodable)]
#[non_exhaustive]
pub struct ExtraInfo {
pub extra_info: ExtraInfoIntroItem,
pub identity_ed25519: EmbeddedCert<Ed25519IdentityCert, KeyUnknownCert>,
#[deftly(netdoc(single_arg))]
pub published: Iso8601TimeSp,
}
#[derive(Debug, Clone, PartialEq, Deftly)]
#[derive_deftly(NetdocParseableSignatures, NetdocEncodable)]
#[deftly(netdoc(signatures(hashes_accu = "RouterHashAccu")))]
#[non_exhaustive]
pub struct ExtraInfoSignatures {
pub router_sig_ed25519: RouterSigEd25519,
pub router_signature: RouterSignature,
}
#[derive(Debug, Clone, PartialEq, Deftly)]
#[derive_deftly(ItemValueParseable, ItemValueEncodable)]
#[non_exhaustive]
pub struct ExtraInfoIntroItem {
pub nickname: Nickname,
pub fingerprint: Fingerprint,
}
impl ExtraInfoUnverified {
pub fn verify(self, rd: &RouterDesc) -> Result<ExtraInfo, VerifyFailed> {
let (mut body, sigs): (ExtraInfo, _) = (self.body, self.sigs);
if rd.router.nickname != body.extra_info.nickname {
return Err(VerifyFailed::Inconsistent);
}
if rd.signing_key.to_rsa_identity() != body.extra_info.fingerprint.0 {
return Err(VerifyFailed::Inconsistent);
}
if rd.identity_ed25519.raw_unverified() != body.identity_ed25519.raw_unverified() {
return Err(VerifyFailed::Inconsistent);
}
body.identity_ed25519 = rd.identity_ed25519.clone();
let sign_ed25519 = body
.identity_ed25519
.get()
.expect("rd must contain verified identity_ed25519")
.sign_ed25519;
if body.published != rd.published {
return Err(VerifyFailed::Inconsistent);
}
let this_sha1 = sigs.hashes.sha1.ok_or(VerifyFailed::VerifyFailed)?;
let this_sha256 = sigs.hashes.sha256.ok_or(VerifyFailed::VerifyFailed)?;
let Some(expected_digests) = rd.extra_info_digest.as_ref() else {
return Err(VerifyFailed::Inconsistent);
};
if *expected_digests.sha1 != this_sha1 {
return Err(VerifyFailed::Inconsistent);
}
ed25519::PublicKey::try_from(sign_ed25519)
.map_err(|_| VerifyFailed::Other)?
.verify(&this_sha256, &sigs.sigs.router_sig_ed25519.0)?;
rd.signing_key
.verify(&this_sha1, &sigs.sigs.router_signature.0)?;
Ok(body)
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::string_slice)]
use std::collections::HashMap;
use tor_checkable::TimeBound;
use super::*;
use crate::doc::routerdesc::RouterDescUnverified;
use crate::parse2::{self, NetdocParseableUnverified, ParseInput};
#[test]
fn simple() {
let routers = parse2::parse_netdoc_multiple::<RouterDescUnverified>(&ParseInput::new(
include_str!("../../testdata2/cached-descriptors.new"),
"cached-descriptors",
))
.unwrap()
.into_iter()
.map(|rd| rd.verify().unwrap().dangerously_assume_timely())
.collect::<Vec<RouterDesc>>();
let extras_list = parse2::parse_netdoc_multiple::<ExtraInfoUnverified>(&ParseInput::new(
include_str!("../../testdata2/cached-extrainfo.new"),
"cached-extrainfo",
))
.unwrap();
let mut extras = HashMap::new();
for extra in extras_list {
let sha1 = extra.inspect_unverified().1.hashes.sha1.unwrap();
assert!(extras.insert(sha1, extra).is_none());
}
for router in routers {
let sha1 = router.extra_info_digest.clone().unwrap().sha1.0;
let extra = extras.remove(&sha1).unwrap();
extra.verify(&router).unwrap();
}
assert!(extras.is_empty());
}
}