pub use b16impl::*;
pub use b64impl::*;
pub use contact_info::*;
pub use curve25519impl::*;
pub use ed25519impl::*;
pub(crate) use edcert::*;
pub use fingerprint::*;
pub use hostname::*;
pub use rsa::*;
pub use timeimpl::*;
pub use nickname::{InvalidNickname, Nickname};
pub use boolean::NumericBoolean;
pub use fingerprint::{Base64Fingerprint, Fingerprint};
pub use identified_digest::{DigestName, IdentifiedDigest};
pub use ignored_impl::{Ignored, IgnoredItemOrObjectValue, NotPresent};
use crate::NormalItemArgument;
use crate::encode::{
self,
ItemArgument,
ItemEncoder,
ItemObjectEncodable,
ItemValueEncodable,
MultiplicitySelector as EMultiplicitySelector,
NetdocEncoder,
};
use crate::parse2::{
self, ArgumentError, ArgumentStream, ItemArgumentParseable, ItemObjectParseable,
ItemValueParseable, SignatureHashInputs, SignatureItemParseable, UnparsedItem,
multiplicity::{
ItemSetMethods,
MultiplicitySelector as P2MultiplicitySelector,
ObjectSetMethods,
},
sig_hashes::Sha1WholeKeywordLine,
};
use derive_deftly::{Deftly, define_derive_deftly, define_derive_deftly_module};
use digest::Digest as _;
use educe::Educe;
use std::cmp::{self, PartialOrd};
use std::fmt::{self, Display};
use std::iter;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::result::Result as StdResult;
use std::str::FromStr;
use subtle::{Choice, ConstantTimeEq};
use tor_error::{Bug, ErrorReport as _, internal, into_internal};
use void::{ResultVoidExt as _, Void};
pub(crate) trait FromBytes: Sized {
fn from_bytes(b: &[u8], p: crate::Pos) -> crate::Result<Self>;
fn from_vec(v: Vec<u8>, p: crate::Pos) -> crate::Result<Self> {
Self::from_bytes(&v[..], p)
}
}
define_derive_deftly_module! {
Transparent beta_deftly:
${define TRANSPARENT_DOCS_IMPLS {
}}
${define TRANSPARENT_IMPLS {
${for fields {
${loop_exactly_1 "must be applied to a single-field struct"}
impl<$tgens> From<$ftype> for $ttype {
fn from($fpatname: $ftype) -> $ttype {
$vpat
}
}
impl<$tgens> From<$ttype> for $ftype {
fn from(self_: $ttype) -> $ftype {
self_.$fname
}
}
impl<$tgens> Deref for $ttype {
type Target = $ftype;
fn deref(&self) -> &$ftype {
&self.$fname
}
}
impl<$tgens> DerefMut for $ttype {
fn deref_mut(&mut self) -> &mut $ftype {
&mut self.$fname
}
}
impl<$tgens> AsRef<$ftype> for $ttype {
fn as_ref(&self) -> &$ftype {
&self.$fname
}
}
impl<$tgens> AsMut<$ftype> for $ttype {
fn as_mut(&mut self) -> &mut $ftype {
&mut self.$fname
}
}
}}
}}
}
define_derive_deftly! {
use Transparent;
$TRANSPARENT_DOCS_IMPLS
Transparent for struct, beta_deftly:
$TRANSPARENT_IMPLS
}
define_derive_deftly! {
use Transparent;
${TRANSPARENT_DOCS_IMPLS}
BytesTransparent for struct, beta_deftly:
$TRANSPARENT_IMPLS
impl<$tgens> ConstantTimeEq for $ttype {
fn ct_eq(&self, other: &$ttype) -> Choice {
$(
self.$fname.ct_eq(&other.$fname)
)
}
}
$ impl<$tgens> PartialEq for $ttype {
fn eq(&self, other: &$ttype) -> bool {
self.ct_eq(other).into()
}
}
impl<$tgens> Eq for $ttype {}
impl<$tgens> $ttype {
pub fn as_bytes(&self) -> &[u8] {
$(
&self.$fname[..]
)
}
}
impl<$tgens> AsRef<[u8]> for $ttype {
fn as_ref(&self) -> &[u8] {
$(
self.$fname.as_ref()
)
}
}
impl<$tgens> AsMut<[u8]> for $ttype {
fn as_mut(&mut self) -> &mut [u8] {
$(
self.$fname.as_mut()
)
}
}
}
mod b64impl {
use super::*;
use crate::{Error, NetdocErrorKind as EK, Pos, Result};
use base64ct::{Base64, Base64Unpadded, Encoding};
use std::ops::RangeBounds;
#[derive(Clone, Hash, Deftly)]
#[derive_deftly(BytesTransparent)]
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(derive_more::Debug)]
#[debug(r#"B64("{self}")"#)]
#[allow(clippy::exhaustive_structs)]
pub struct B64(pub Vec<u8>);
impl FromStr for B64 {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let v: core::result::Result<Vec<u8>, base64ct::Error> = match s.len() % 4 {
0 => Base64::decode_vec(s),
_ => Base64Unpadded::decode_vec(s),
};
let v = v.map_err(|_| {
EK::BadArgument
.with_msg("Invalid base64")
.at_pos(Pos::at(s))
})?;
Ok(B64(v))
}
}
impl Display for B64 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&Base64Unpadded::encode_string(&self.0), f)
}
}
impl B64 {
pub(crate) fn check_len<B: RangeBounds<usize>>(self, bounds: B) -> Result<Self> {
if bounds.contains(&self.0.len()) {
Ok(self)
} else {
Err(EK::BadObjectVal.with_msg("Invalid length on base64 data"))
}
}
pub(crate) fn into_array<const N: usize>(self) -> Result<[u8; N]> {
self.0
.try_into()
.map_err(|_| EK::BadObjectVal.with_msg("Invalid length on base64 data"))
}
}
impl FromIterator<u8> for B64 {
fn from_iter<T: IntoIterator<Item = u8>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl NormalItemArgument for B64 {}
#[derive(Clone, Hash, Deftly)]
#[derive_deftly(BytesTransparent)]
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(derive_more::Debug)]
#[debug(r#"FixedB64::<{N}>("{self}")"#)]
#[allow(clippy::exhaustive_structs)]
pub struct FixedB64<const N: usize>(pub [u8; N]);
impl<const N: usize> Display for FixedB64<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&B64(self.0.to_vec()), f)
}
}
impl<const N: usize> FromStr for FixedB64<N> {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Ok(Self(B64::from_str(s)?.0.try_into().map_err(|_| {
EK::BadArgument
.at_pos(Pos::at(s))
.with_msg("invalid length")
})?))
}
}
impl<const N: usize> NormalItemArgument for FixedB64<N> {}
}
mod b16impl {
use super::*;
use crate::{Error, NetdocErrorKind as EK, Pos, Result};
#[derive(Clone, Hash, Deftly)]
#[derive_deftly(BytesTransparent)]
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(derive_more::Debug)]
#[debug(r#"B16("{self}")"#)]
#[allow(clippy::exhaustive_structs)]
pub struct B16(pub Vec<u8>);
#[derive(Clone, Hash, Deftly)]
#[derive_deftly(BytesTransparent)]
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(derive_more::Debug)]
#[debug(r#"B16U("{self}")"#)]
#[allow(clippy::exhaustive_structs)]
pub struct B16U(pub Vec<u8>);
#[derive(Clone, Hash, Deftly)]
#[derive_deftly(BytesTransparent)]
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(derive_more::Debug)]
#[debug(r#"FixedB16U("{self}")"#)]
#[allow(clippy::exhaustive_structs)]
pub struct FixedB16U<const N: usize>(pub [u8; N]);
impl FromStr for B16 {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let bytes = hex::decode(s).map_err(|_| {
EK::BadArgument
.at_pos(Pos::at(s))
.with_msg("invalid hexadecimal")
})?;
Ok(B16(bytes))
}
}
impl FromStr for B16U {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Ok(B16U(B16::from_str(s)?.0))
}
}
impl<const N: usize> FromStr for FixedB16U<N> {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Ok(Self(B16U::from_str(s)?.0.try_into().map_err(|_| {
EK::BadArgument
.at_pos(Pos::at(s))
.with_msg("invalid length")
})?))
}
}
impl Display for B16 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for c in self.as_bytes() {
write!(f, "{c:02x}")?;
}
Ok(())
}
}
impl Display for B16U {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for c in self.as_bytes() {
write!(f, "{c:02X}")?;
}
Ok(())
}
}
impl<const N: usize> Display for FixedB16U<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for c in self.as_bytes() {
write!(f, "{c:02X}")?;
}
Ok(())
}
}
impl NormalItemArgument for B16 {}
impl NormalItemArgument for B16U {}
impl<const N: usize> NormalItemArgument for FixedB16U<N> {}
}
mod curve25519impl {
use super::*;
use crate::{Error, NormalItemArgument, Result, types::misc::FixedB64};
use tor_llcrypto::pk::curve25519::PublicKey;
#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
#[derive_deftly(Transparent)]
#[allow(clippy::exhaustive_structs)]
pub struct Curve25519Public(pub PublicKey);
impl FromStr for Curve25519Public {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let pk: FixedB64<32> = s.parse()?;
let pk: [u8; 32] = pk.into();
Ok(Curve25519Public(pk.into()))
}
}
impl Display for Curve25519Public {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
FixedB64::from(self.0.to_bytes()).fmt(f)
}
}
impl NormalItemArgument for Curve25519Public {}
}
mod ed25519impl {
use super::*;
use crate::{Error, NormalItemArgument, Result, types::misc::FixedB64};
use derive_deftly::Deftly;
use tor_llcrypto::pk::ed25519::{Ed25519Identity, Signature};
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::exhaustive_structs)]
pub struct Ed25519Public(pub Ed25519Identity);
impl FromStr for Ed25519Public {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let pk: FixedB64<32> = s.parse()?;
Ok(Ed25519Public(Ed25519Identity::new(pk.into())))
}
}
impl Display for Ed25519Public {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let pk: [u8; 32] = self.0.into();
let pk = FixedB64::from(pk);
pk.fmt(f)
}
}
impl NormalItemArgument for Ed25519Public {}
impl From<Ed25519Public> for Ed25519Identity {
fn from(pk: Ed25519Public) -> Ed25519Identity {
pk.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display, derive_more::FromStr)]
#[display(rename_all = "lowercase")]
#[from_str(rename_all = "lowercase")]
#[allow(clippy::exhaustive_enums)]
pub enum Ed25519AlgorithmString {
Ed25519,
}
impl NormalItemArgument for Ed25519AlgorithmString {}
#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
#[derive_deftly(ItemValueParseable)]
#[non_exhaustive]
pub struct Ed25519IdentityLine {
pub alg: Ed25519AlgorithmString,
pub pk: Ed25519Public,
}
impl From<Ed25519Public> for Ed25519IdentityLine {
fn from(pk: Ed25519Public) -> Self {
Self {
alg: Ed25519AlgorithmString::Ed25519,
pk,
}
}
}
impl From<Ed25519Identity> for Ed25519IdentityLine {
fn from(pk: Ed25519Identity) -> Self {
Ed25519Public(pk).into()
}
}
impl ItemArgument for Signature {
fn write_arg_onto(&self, out: &mut ItemEncoder) -> StdResult<(), Bug> {
FixedB64::from(self.to_bytes()).write_arg_onto(out)
}
}
}
mod ignored_impl {
use super::*;
use crate::parse2::ErrorProblem as EP;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
#[allow(clippy::exhaustive_structs)]
#[derive(Deftly)]
#[derive_deftly(NetdocParseableFields)]
pub struct NotPresent;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default, Deftly)]
#[derive_deftly(ItemValueParseable, NetdocParseableFields)]
#[allow(clippy::exhaustive_structs)]
pub struct Ignored;
pub struct IgnoredItemOrObjectValue(Void);
impl ItemSetMethods for P2MultiplicitySelector<NotPresent> {
type Each = Ignored;
type Field = NotPresent;
fn can_accumulate(self, _acc: &Option<NotPresent>) -> Result<(), EP> {
Ok(())
}
fn accumulate(self, _acc: &mut Option<NotPresent>, _item: Ignored) -> Result<(), EP> {
Ok(())
}
fn finish(self, _acc: Option<NotPresent>, _: &'static str) -> Result<NotPresent, EP> {
Ok(NotPresent)
}
fn debug_core(self) -> &'static str {
"Ignored"
}
}
impl ItemArgumentParseable for NotPresent {
fn from_args(_: &mut ArgumentStream) -> Result<NotPresent, ArgumentError> {
Ok(NotPresent)
}
}
impl ObjectSetMethods for P2MultiplicitySelector<NotPresent> {
type Field = NotPresent;
type Each = Void;
fn resolve_option(self, _found: Option<Void>) -> Result<NotPresent, EP> {
Ok(NotPresent)
}
fn debug_core(self) -> &'static str {
"NotPresent"
}
}
impl<'f> encode::MultiplicityMethods<'f> for EMultiplicitySelector<NotPresent> {
type Field = NotPresent;
type Each = Void;
fn iter_ordered(self, _: &'f Self::Field) -> impl Iterator<Item = &'f Self::Each> {
iter::empty()
}
}
impl encode::OptionalityMethods for EMultiplicitySelector<NotPresent> {
type Field = NotPresent;
type Each = Void;
fn as_option<'f>(self, _: &'f Self::Field) -> Option<&'f Self::Each> {
None
}
}
impl FromStr for Ignored {
type Err = Void;
fn from_str(_s: &str) -> Result<Ignored, Void> {
Ok(Ignored)
}
}
impl ItemArgumentParseable for Ignored {
fn from_args(_: &mut ArgumentStream) -> Result<Ignored, ArgumentError> {
Ok(Ignored)
}
}
impl ItemObjectParseable for Ignored {
fn check_label(_label: &str) -> Result<(), EP> {
Ok(())
}
fn from_bytes(_input: &[u8]) -> Result<Self, EP> {
Ok(Ignored)
}
}
impl ObjectSetMethods for P2MultiplicitySelector<Ignored> {
type Field = Ignored;
type Each = Ignored;
fn resolve_option(self, _found: Option<Ignored>) -> Result<Ignored, EP> {
Ok(Ignored)
}
fn debug_core(self) -> &'static str {
"Ignored"
}
}
impl<'f> encode::MultiplicityMethods<'f> for EMultiplicitySelector<Ignored> {
type Field = Ignored;
type Each = IgnoredItemOrObjectValue;
fn iter_ordered(self, _: &'f Self::Field) -> impl Iterator<Item = &'f Self::Each> {
iter::empty()
}
}
impl encode::OptionalityMethods for EMultiplicitySelector<Ignored> {
type Field = Ignored;
type Each = IgnoredItemOrObjectValue;
fn as_option<'f>(self, _: &'f Self::Field) -> Option<&'f Self::Each> {
None
}
}
impl ItemValueEncodable for IgnoredItemOrObjectValue {
fn write_item_value_onto(&self, _: ItemEncoder) -> Result<(), Bug> {
void::unreachable(self.0)
}
}
impl ItemObjectEncodable for IgnoredItemOrObjectValue {
fn label(&self) -> &str {
void::unreachable(self.0)
}
fn write_object_onto(&self, _: &mut Vec<u8>) -> Result<(), Bug> {
void::unreachable(self.0)
}
}
}
#[derive(Debug, PartialEq, Clone, Copy, Hash)]
#[allow(clippy::exhaustive_enums)] pub enum Unknown<T> {
Discarded(PhantomData<T>),
#[cfg(feature = "retain-unknown")]
Retained(T),
}
impl<T> Unknown<T> {
pub fn new_discard() -> Self {
Unknown::Discarded(PhantomData)
}
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Unknown<U> {
self.try_map(move |t| Ok::<_, Void>(f(t))).void_unwrap()
}
pub fn try_map<U, E>(self, f: impl FnOnce(T) -> Result<U, E>) -> Result<Unknown<U>, E> {
Ok(match self {
Unknown::Discarded(_) => Unknown::Discarded(PhantomData),
#[cfg(feature = "retain-unknown")]
Unknown::Retained(t) => Unknown::Retained(f(t)?),
})
}
pub fn as_ref(&self) -> Unknown<&T> {
match self {
Unknown::Discarded(_) => Unknown::Discarded(PhantomData),
#[cfg(feature = "retain-unknown")]
Unknown::Retained(t) => Unknown::Retained(t),
}
}
pub fn only_known(self) -> Option<T> {
match self {
Unknown::Discarded(_) => None,
#[cfg(feature = "retain-unknown")]
Unknown::Retained(t) => Some(t),
}
}
#[cfg(feature = "retain-unknown")]
pub fn into_retained(self) -> Result<T, Bug> {
match self {
Unknown::Discarded(_) => Err(internal!("Unknown::retained but data not collected")),
Unknown::Retained(t) => Ok(t),
}
}
#[cfg(feature = "retain-unknown")]
pub fn new_retained_default() -> Self
where
T: Default,
{
Unknown::Retained(T::default())
}
pub fn with_mut_unknown(&mut self, f: impl FnOnce(&mut T)) {
match self {
Unknown::Discarded(_) => {}
#[cfg(feature = "retain-unknown")]
Unknown::Retained(t) => f(t),
}
}
}
impl<T: PartialOrd> PartialOrd for Unknown<T> {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
use Unknown::*;
match (self, other) {
(Discarded(_), Discarded(_)) => Some(cmp::Ordering::Equal),
#[cfg(feature = "retain-unknown")]
(Discarded(_), Retained(_)) | (Retained(_), Discarded(_)) => None,
#[cfg(feature = "retain-unknown")]
(Retained(a), Retained(b)) => a.partial_cmp(b),
}
}
}
#[derive(Debug, PartialEq, Clone, Hash)]
#[allow(clippy::exhaustive_enums)] pub enum KeywordOrString<T: Copy> {
Known(T),
Unknown(String),
}
impl<T: Copy + NormalItemArgument> NormalItemArgument for KeywordOrString<T> {}
impl<T: Copy + Display> Display for KeywordOrString<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
KeywordOrString::Known(t) => Display::fmt(t, f),
KeywordOrString::Unknown(s) => Display::fmt(s, f),
}
}
}
impl<T: Copy + FromStr> FromStr for KeywordOrString<T> {
type Err = Void;
fn from_str(s: &str) -> Result<Self, Void> {
Ok(match s.parse() {
Ok(y) => KeywordOrString::Known(y),
Err(_) => KeywordOrString::Unknown(s.to_owned()),
})
}
}
#[derive(Debug, Clone, Hash, Deftly, Eq, PartialEq, Educe)]
#[educe(Default)]
#[derive_deftly(Transparent)]
#[allow(clippy::exhaustive_structs)]
pub struct RetainedOrderVec<T>(pub Vec<T>);
mod timeimpl {
use super::*;
use crate::{Error, NetdocErrorKind as EK, Pos, Result};
use std::time::SystemTime;
use time::{
OffsetDateTime, PrimitiveDateTime, format_description::FormatItem,
macros::format_description,
};
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deftly)]
#[derive_deftly(Transparent)]
#[allow(clippy::exhaustive_structs)]
pub struct Iso8601TimeSp(pub SystemTime);
const ISO_8601SP_FMT: &[FormatItem] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
impl FromStr for Iso8601TimeSp {
type Err = Error;
fn from_str(s: &str) -> Result<Iso8601TimeSp> {
let d = PrimitiveDateTime::parse(s, &ISO_8601SP_FMT).map_err(|e| {
EK::BadArgument
.at_pos(Pos::at(s))
.with_msg(format!("invalid time: {}", e))
})?;
Ok(Iso8601TimeSp(d.assume_utc().into()))
}
}
fn fmt_with(
t: SystemTime,
format_desc: &[FormatItem],
) -> core::result::Result<String, fmt::Error> {
OffsetDateTime::from(t)
.format(format_desc)
.map_err(|_| fmt::Error)
}
impl Display for Iso8601TimeSp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", fmt_with(self.0, ISO_8601SP_FMT)?)
}
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deftly)]
#[derive_deftly(Transparent)]
#[allow(clippy::exhaustive_structs)]
pub struct Iso8601TimeNoSp(pub SystemTime);
const ISO_8601NOSP_FMT: &[FormatItem] =
format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
impl FromStr for Iso8601TimeNoSp {
type Err = Error;
fn from_str(s: &str) -> Result<Iso8601TimeNoSp> {
let d = PrimitiveDateTime::parse(s, &ISO_8601NOSP_FMT).map_err(|e| {
EK::BadArgument
.at_pos(Pos::at(s))
.with_msg(format!("invalid time: {}", e))
})?;
Ok(Iso8601TimeNoSp(d.assume_utc().into()))
}
}
impl Display for Iso8601TimeNoSp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", fmt_with(self.0, ISO_8601NOSP_FMT)?)
}
}
impl crate::NormalItemArgument for Iso8601TimeNoSp {}
}
mod rsa {
use super::*;
use crate::{NetdocErrorKind as EK, Pos, Result};
use std::ops::RangeBounds;
use tor_llcrypto::pk::rsa::PublicKey;
use tor_llcrypto::{d::Sha1, pk::rsa::KeyPair};
pub(crate) const RSA_FIXED_EXPONENT: u32 = 65537;
pub(crate) const RSA_MIN_BITS: usize = 1024;
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub(crate) struct RsaPublicParse1Helper(PublicKey, Pos);
#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
#[derive_deftly(ItemValueParseable, ItemValueEncodable)]
#[deftly(netdoc(no_extra_args, signature(hash_accu = Sha1WholeKeywordLine)))]
#[non_exhaustive]
pub struct RsaSha1Signature {
#[deftly(netdoc(object(label = "SIGNATURE"), with = crate::types::raw_data_object))]
pub signature: Vec<u8>,
}
impl From<RsaPublicParse1Helper> for PublicKey {
fn from(k: RsaPublicParse1Helper) -> PublicKey {
k.0
}
}
impl super::FromBytes for RsaPublicParse1Helper {
fn from_bytes(b: &[u8], pos: Pos) -> Result<Self> {
let key = PublicKey::from_der(b)
.ok_or_else(|| EK::BadObjectVal.with_msg("unable to decode RSA public key"))?;
Ok(RsaPublicParse1Helper(key, pos))
}
}
impl RsaPublicParse1Helper {
pub(crate) fn check_exponent(self, e: u32) -> Result<Self> {
if self.0.exponent_is(e) {
Ok(self)
} else {
Err(EK::BadObjectVal
.at_pos(self.1)
.with_msg("invalid RSA exponent"))
}
}
pub(crate) fn check_len<B: RangeBounds<usize>>(self, bounds: B) -> Result<Self> {
if bounds.contains(&self.0.bits()) {
Ok(self)
} else {
Err(EK::BadObjectVal
.at_pos(self.1)
.with_msg("invalid RSA length"))
}
}
pub(crate) fn check_len_eq(self, n: usize) -> Result<Self> {
self.check_len(n..=n)
}
}
impl RsaSha1Signature {
pub fn new_sign_netdoc(
private_key: &KeyPair,
encoder: &NetdocEncoder,
item_keyword: &str,
) -> StdResult<Self, Bug> {
let mut h = Sha1::new();
h.update(encoder.text_sofar()?);
h.update(item_keyword);
h.update("\n");
let h = h.finalize();
let signature = private_key
.sign(&h)
.map_err(into_internal!("RSA signing failed"))?;
Ok(RsaSha1Signature { signature })
}
}
}
mod edcert {
use crate::{NetdocErrorKind as EK, Pos, Result};
use tor_cert::{CertType, Ed25519Cert, KeyUnknownCert};
use tor_llcrypto::pk::ed25519;
#[derive(Debug, Clone)]
pub(crate) struct UnvalidatedEdCert(KeyUnknownCert, Pos);
impl super::FromBytes for UnvalidatedEdCert {
fn from_bytes(b: &[u8], p: Pos) -> Result<Self> {
let cert = Ed25519Cert::decode(b).map_err(|e| {
EK::BadObjectVal
.at_pos(p)
.with_msg("Bad certificate")
.with_source(e)
})?;
Ok(Self(cert, p))
}
fn from_vec(v: Vec<u8>, p: Pos) -> Result<Self> {
Self::from_bytes(&v[..], p)
}
}
impl UnvalidatedEdCert {
pub(crate) fn check_cert_type(self, desired_type: CertType) -> Result<Self> {
if self.0.peek_cert_type() != desired_type {
return Err(EK::BadObjectVal.at_pos(self.1).with_msg(format!(
"bad certificate type {} (wanted {})",
self.0.peek_cert_type(),
desired_type
)));
}
Ok(self)
}
pub(crate) fn check_subject_key_is(self, pk: &ed25519::Ed25519Identity) -> Result<Self> {
if self.0.peek_subject_key().as_ed25519() != Some(pk) {
return Err(EK::BadObjectVal
.at_pos(self.1)
.with_msg("incorrect subject key"));
}
Ok(self)
}
pub(crate) fn into_unchecked(self) -> KeyUnknownCert {
self.0
}
}
}
mod identified_digest {
use super::*;
define_derive_deftly! {
StringReprUnitsOrUnknown for enum, expect items, beta_deftly:
${define STRING_REPR {
${vmeta(string_repr)
as str,
default { ${concat ${snake_case $vname}} }
}
}}
impl FromStr for $ttype {
type Err = Void;
fn from_str(s: &str) -> Result<Self, Void> {
$(
${when v_is_unit}
if s == $STRING_REPR {
return Ok($vtype)
}
)
$(
${when not(v_is_unit)} Ok($vtype { 0: s.into() })
)
}
}
impl Display for $ttype {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s: &str = match self {
$(
${when v_is_unit}
$vtype => $STRING_REPR,
)
$(
${when not(v_is_unit)}
$vpat => f_0,
)
};
Display::fmt(s, f)
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
#[derive_deftly(StringReprUnitsOrUnknown)]
#[non_exhaustive]
pub enum DigestName {
Sha256,
Unknown(String),
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, derive_more::Display)]
#[display("{alg}={value}")]
#[non_exhaustive]
pub struct IdentifiedDigest {
alg: DigestName,
value: B64,
}
impl NormalItemArgument for DigestName {}
impl NormalItemArgument for IdentifiedDigest {}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, thiserror::Error)]
#[error("invalid syntax, expected ALGORITHM=DIGEST: {0}")]
pub struct IdentifiedDigestParseError(String);
impl FromStr for IdentifiedDigest {
type Err = IdentifiedDigestParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
(|| {
let (alg, value) = s.split_once('=').ok_or("missing equals sign")?;
let alg = alg.parse().void_unwrap();
let value = value
.parse::<B64>()
.map_err(|e| format!("bad value: {}", e.report()))?;
if let Some(exp_len) = (|| {
Some({
use DigestName::*;
match alg {
Sha256 => 32,
Unknown(_) => None?,
}
})
})() {
let val_len = value.as_bytes().len();
if val_len != exp_len {
return Err(format!("got {val_len} bytes, expected {exp_len}"));
}
}
Ok(IdentifiedDigest { alg, value })
})()
.map_err(IdentifiedDigestParseError)
}
}
}
mod fingerprint {
use super::*;
use crate::parse2::{ArgumentError, ArgumentStream, ItemArgumentParseable};
use crate::{Error, NetdocErrorKind as EK, Pos, Result};
use base64ct::{Base64Unpadded, Encoding as _};
use tor_llcrypto::pk::rsa::RsaIdentity;
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
#[derive_deftly(Transparent)]
#[allow(clippy::exhaustive_structs)]
pub struct SpFingerprint(pub RsaIdentity);
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
#[derive_deftly(Transparent)]
#[allow(clippy::exhaustive_structs)]
pub struct Fingerprint(pub RsaIdentity);
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
#[derive_deftly(Transparent)]
#[allow(clippy::exhaustive_structs)]
pub struct Base64Fingerprint(pub RsaIdentity);
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
#[derive_deftly(Transparent)]
#[allow(clippy::exhaustive_structs)]
pub(crate) struct LongIdent(pub RsaIdentity);
fn parse_hex_ident(s: &str) -> Result<RsaIdentity> {
RsaIdentity::from_hex(s).ok_or_else(|| {
EK::BadArgument
.at_pos(Pos::at(s))
.with_msg("wrong length on fingerprint")
})
}
impl FromStr for SpFingerprint {
type Err = Error;
fn from_str(s: &str) -> Result<SpFingerprint> {
let ident = parse_hex_ident(&s.replace(' ', "")).map_err(|e| e.at_pos(Pos::at(s)))?;
Ok(SpFingerprint(ident))
}
}
impl ItemArgumentParseable for SpFingerprint {
fn from_args<'s>(
args: &mut ArgumentStream<'s>,
) -> std::result::Result<Self, ArgumentError> {
let fp = args.take(10).collect::<Vec<_>>();
if fp.len() < 10 {
return Err(ArgumentError::Missing);
}
debug_assert_eq!(fp.len(), 10);
if fp.iter().any(|arg| arg.len() != 4) {
return Err(ArgumentError::Invalid);
}
Ok(Self(
RsaIdentity::from_hex(fp.join("").as_str()).ok_or(ArgumentError::Invalid)?,
))
}
}
impl FromStr for Base64Fingerprint {
type Err = Error;
fn from_str(s: &str) -> Result<Base64Fingerprint> {
let b = s.parse::<super::B64>()?;
let ident = RsaIdentity::from_bytes(b.as_bytes()).ok_or_else(|| {
EK::BadArgument
.at_pos(Pos::at(s))
.with_msg("Wrong identity length")
})?;
Ok(Base64Fingerprint(ident))
}
}
impl Display for Base64Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&Base64Unpadded::encode_string(self.as_bytes()), f)
}
}
impl FromStr for Fingerprint {
type Err = Error;
fn from_str(s: &str) -> Result<Fingerprint> {
let ident = parse_hex_ident(s).map_err(|e| e.at_pos(Pos::at(s)))?;
Ok(Fingerprint(ident))
}
}
impl Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&hex::encode_upper(self.as_bytes()), f)
}
}
impl FromStr for LongIdent {
type Err = Error;
fn from_str(mut s: &str) -> Result<LongIdent> {
if s.starts_with('$') {
s = &s[1..];
}
if let Some(idx) = s.find(['=', '~']) {
s = &s[..idx];
}
let ident = parse_hex_ident(s)?;
Ok(LongIdent(ident))
}
}
impl Display for LongIdent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "${}", self.0.as_hex_upper())
}
}
impl crate::NormalItemArgument for Fingerprint {}
impl crate::NormalItemArgument for Base64Fingerprint {}
impl crate::NormalItemArgument for LongIdent {}
}
mod nickname {
use super::*;
use tinystr::TinyAsciiStr;
const MAX_NICKNAME_LEN: usize = 19;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Nickname(tinystr::TinyAsciiStr<MAX_NICKNAME_LEN>);
#[derive(Clone, Debug, thiserror::Error)]
#[error("invalid nickname")]
#[non_exhaustive]
pub struct InvalidNickname {}
impl Nickname {
pub(crate) fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl Display for Nickname {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl FromStr for Nickname {
type Err = InvalidNickname;
fn from_str(s: &str) -> Result<Self, InvalidNickname> {
let tiny = TinyAsciiStr::from_str(s).map_err(|_| InvalidNickname {})?;
if tiny.is_ascii_alphanumeric() && !tiny.is_empty() {
Ok(Nickname(tiny))
} else {
Err(InvalidNickname {})
}
}
}
impl crate::NormalItemArgument for Nickname {}
}
mod hostname {
use super::*;
use std::net::IpAddr;
#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[derive(derive_more::Into, derive_more::Deref, derive_more::AsRef, derive_more::Display)]
pub struct Hostname(String);
#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[derive(derive_more::Display)]
#[allow(clippy::exhaustive_enums)]
pub enum InternetHost {
#[display("{_0}")]
Name(Hostname),
#[display("{_0}")]
IpAddr(IpAddr),
}
#[derive(Clone, Debug, thiserror::Error)]
#[error("invalid hostname")]
#[non_exhaustive]
pub struct InvalidHostname {}
#[derive(Clone, Debug, thiserror::Error)]
#[error("invalid: not a valid hostname, nor a valid IPv4 or IPv6 address")]
#[non_exhaustive]
pub struct InvalidInternetHost {}
impl Hostname {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for Hostname {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl TryFrom<String> for Hostname {
type Error = InvalidHostname;
fn try_from(s: String) -> Result<Self, InvalidHostname> {
if hostname_validator::is_valid(&s) &&
!s.chars().all(|c| c.is_ascii_digit() || c == '.')
{
Ok(Hostname(s))
} else {
Err(InvalidHostname {})
}
}
}
impl FromStr for Hostname {
type Err = InvalidHostname;
fn from_str(s: &str) -> Result<Self, InvalidHostname> {
s.to_owned().try_into()
}
}
impl FromStr for InternetHost {
type Err = InvalidInternetHost;
fn from_str(s: &str) -> Result<Self, InvalidInternetHost> {
if let Ok(y) = s.parse() {
Ok(InternetHost::IpAddr(y))
} else if let Ok(y) = s.parse() {
Ok(InternetHost::Name(y))
} else {
Err(InvalidInternetHost {})
}
}
}
impl NormalItemArgument for Hostname {}
impl NormalItemArgument for InternetHost {}
}
mod contact_info {
use super::*;
#[derive(Clone, Debug, PartialEq, Eq, Deftly)] #[derive(derive_more::Into, derive_more::AsRef, derive_more::Deref, derive_more::Display)]
#[derive_deftly(ItemValueEncodable)]
#[non_exhaustive]
pub struct ContactInfo(#[deftly(netdoc(rest))] String);
#[derive(Clone, Debug, thiserror::Error)]
#[error("contact information (`contact` item value) has invalid syntax")]
#[non_exhaustive]
pub struct InvalidContactInfo {}
impl FromStr for ContactInfo {
type Err = InvalidContactInfo;
fn from_str(s: &str) -> Result<Self, InvalidContactInfo> {
if s.contains('\n') || s.starts_with(char::is_whitespace) {
Err(InvalidContactInfo {})
} else {
Ok(ContactInfo(s.to_owned()))
}
}
}
impl ItemValueParseable for ContactInfo {
fn from_unparsed(mut item: UnparsedItem<'_>) -> Result<Self, parse2::ErrorProblem> {
item.check_no_object()?;
item.args_mut()
.into_remaining()
.parse()
.map_err(|_e| item.args().handle_error("info", ArgumentError::Invalid))
}
}
}
mod boolean {
use std::{fmt::Display, str::FromStr};
use derive_more::{From, Into};
use crate::{Error, NetdocErrorKind as EK, NormalItemArgument, Pos};
#[derive(Clone, Copy, Debug, Default, From, Into)]
#[allow(clippy::exhaustive_structs)]
pub struct NumericBoolean(pub bool);
impl FromStr for NumericBoolean {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"0" => Ok(Self(false)),
"1" => Ok(Self(true)),
_ => Err(EK::BadArgument
.at_pos(Pos::at(s))
.with_msg("Invalid numeric boolean")),
}
}
}
impl Display for NumericBoolean {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", u8::from(self.0))
}
}
impl NormalItemArgument for NumericBoolean {}
}
pub mod routerdesc {
use super::*;
use parse2::ErrorProblem as EP;
use tor_llcrypto::pk::ed25519;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
#[non_exhaustive]
pub enum OverloadGeneralVersion {
#[strum(serialize = "1")]
V1,
}
impl NormalItemArgument for OverloadGeneralVersion {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deftly)]
#[derive_deftly(ItemValueParseable)]
#[non_exhaustive]
pub struct OverloadGeneral {
pub version: OverloadGeneralVersion,
pub since: Iso8601TimeSp,
}
#[derive(Clone, Debug, PartialEq, Eq, Deftly)]
#[derive_deftly(ItemValueParseable)]
#[non_exhaustive]
pub struct RouterDescIntroItem {
pub nickname: Nickname,
pub address: std::net::Ipv4Addr,
pub orport: u16,
pub socksport: u16,
pub dirport: u16,
}
#[derive(Clone, Debug, PartialEq, Eq, Deftly)]
#[derive_deftly(ItemValueParseable)]
#[non_exhaustive]
pub struct ExtraInfoDigests {
pub sha1: FixedB16U<20>,
pub sha2: Option<FixedB64<32>>,
}
#[derive(Debug, Clone, Default, Deftly)]
#[derive_deftly(AsMutSelf)]
#[allow(clippy::exhaustive_structs)]
pub struct RouterHashAccu {
pub sha1: Option<[u8; 20]>,
pub sha256: Option<[u8; 32]>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
#[derive_deftly(ItemValueEncodable)]
#[allow(clippy::exhaustive_structs)]
pub struct RouterSigEd25519(pub ed25519::Signature);
impl RouterSigEd25519 {
const HASH_PREFIX_MAGIC: &str = "Tor router descriptor signature v1";
fn hash(document_sofar: &str, signature_item_kw_spc: &[&str]) -> [u8; 32] {
debug_assert!(
signature_item_kw_spc
.last()
.expect("signature_item_kw_spc")
.ends_with(" ")
);
let mut h = tor_llcrypto::d::Sha256::new();
h.update(Self::HASH_PREFIX_MAGIC);
h.update(document_sofar);
for b in signature_item_kw_spc {
h.update(b);
}
h.finalize().into()
}
pub fn new_sign_netdoc(
private_key: &ed25519::Keypair,
encoder: &NetdocEncoder,
item_keyword: &str,
) -> StdResult<Self, Bug> {
let signature = private_key
.sign(&Self::hash(encoder.text_sofar()?, &[item_keyword, " "]))
.to_bytes()
.into();
Ok(RouterSigEd25519(signature))
}
}
impl SignatureItemParseable for RouterSigEd25519 {
type HashAccu = RouterHashAccu;
fn from_unparsed_and_body(
mut item: UnparsedItem<'_>,
hash_inputs: &SignatureHashInputs<'_>,
hash: &mut Self::HashAccu,
) -> Result<Self, EP> {
let args = item.args_mut();
let sig = FixedB64::<64>::from_args(args)
.map_err(|e| args.handle_error("router-sig-ed25519", e))?
.0;
let sig = ed25519::Signature::from(sig);
hash.sha256 = Some(Self::hash(
hash_inputs.document_sofar,
&[hash_inputs.signature_item_kw_spc],
));
Ok(Self(sig))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::exhaustive_structs)]
pub struct RouterSignature(pub Vec<u8>);
impl SignatureItemParseable for RouterSignature {
type HashAccu = RouterHashAccu;
fn from_unparsed_and_body(
mut item: UnparsedItem<'_>,
hash_inputs: &SignatureHashInputs<'_>,
hash: &mut Self::HashAccu,
) -> Result<Self, EP> {
let args = item.args_mut();
if args.next().is_some() {
return Err(EP::UnexpectedArgument {
column: args.prev_arg_column(),
});
}
let obj = item.object().ok_or(EP::MissingObject)?.decode_data()?;
let mut h = tor_llcrypto::d::Sha1::new();
h.update(hash_inputs.document_sofar);
h.update(hash_inputs.signature_item_line);
h.update("\n");
hash.sha1 = Some(h.finalize().into());
Ok(Self(obj))
}
}
}
#[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)]
use itertools::Itertools;
use base64ct::Encoding;
use super::*;
use crate::{Pos, Result};
fn base64_decode_ignore_ws(s: &str) -> std::result::Result<Vec<u8>, base64ct::Error> {
let mut s = s.to_string();
s.retain(|c| !c.is_ascii_whitespace());
base64ct::Base64::decode_vec(s.as_str())
}
#[test]
fn base64() -> Result<()> {
assert_eq!("Mi43MTgyOA".parse::<B64>()?.as_bytes(), &b"2.71828"[..]);
assert!("Mi43MTgyOA".parse::<B64>()?.check_len(7..8).is_ok());
assert_eq!("Mg".parse::<B64>()?.as_bytes(), &b"2"[..]);
assert!("Mg".parse::<B64>()?.check_len(1..2).is_ok());
assert_eq!(
"8J+NkvCfjZLwn42S8J+NkvCfjZLwn42S"
.parse::<B64>()?
.as_bytes(),
"🍒🍒🍒🍒🍒🍒".as_bytes()
);
assert!(
"8J+NkvCfjZLwn42S8J+NkvCfjZLwn42S"
.parse::<B64>()?
.check_len(24..25)
.is_ok()
);
assert!(
"ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz8="
.parse::<B64>()?
.check_len(32..33)
.is_ok()
);
assert_eq!("Mi43MTgyOA==".parse::<B64>()?.as_bytes(), &b"2.71828"[..]);
assert!("Mi43MTgyOA==".parse::<B64>()?.check_len(7..8).is_ok());
assert_eq!("Mg==".parse::<B64>()?.as_bytes(), &b"2"[..]);
assert!("Mg==".parse::<B64>()?.check_len(1..2).is_ok());
assert!("Mi43!!!!!!".parse::<B64>().is_err());
assert!("Mi".parse::<B64>().is_err());
assert!(
"ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxaaaa"
.parse::<B64>()
.is_err()
);
assert!("Mi43MTgyOA".parse::<B64>()?.check_len(8..).is_err());
Ok(())
}
#[test]
fn base64_lengths() -> Result<()> {
assert_eq!("".parse::<B64>()?.as_bytes(), b"");
assert!("=".parse::<B64>().is_err());
assert!("==".parse::<B64>().is_err());
assert!("B".parse::<B64>().is_err());
assert!("B=".parse::<B64>().is_err());
assert!("B==".parse::<B64>().is_err());
assert!("Bg=".parse::<B64>().is_err());
assert_eq!("Bg".parse::<B64>()?.as_bytes(), b"\x06");
assert_eq!("Bg==".parse::<B64>()?.as_bytes(), b"\x06");
assert_eq!("BCg".parse::<B64>()?.as_bytes(), b"\x04\x28");
assert_eq!("BCg=".parse::<B64>()?.as_bytes(), b"\x04\x28");
assert!("BCg==".parse::<B64>().is_err());
assert_eq!("BCDE".parse::<B64>()?.as_bytes(), b"\x04\x20\xc4");
assert!("BCDE=".parse::<B64>().is_err());
assert!("BCDE==".parse::<B64>().is_err());
Ok(())
}
#[test]
fn base64_rev() {
use base64ct::{Base64, Base64Unpadded};
for n in 0..=5 {
for c_vec in std::iter::repeat_n("ACEQg/=".chars(), n).multi_cartesian_product() {
let s: String = c_vec.into_iter().collect();
#[allow(clippy::print_stderr)]
let b = match s.parse::<B64>() {
Ok(b) => {
eprintln!("{:10} {:?}", &s, b.as_bytes());
b
}
Err(_) => {
eprintln!("{:10} Err", &s);
continue;
}
};
let b = b.as_bytes();
let ep = Base64::encode_string(b);
let eu = Base64Unpadded::encode_string(b);
assert!(
s == ep || s == eu,
"{:?} decoded to {:?} giving neither {:?} nor {:?}",
s,
b,
ep,
eu
);
}
}
}
#[test]
fn base16() -> anyhow::Result<()> {
let chk = |s: &str, b: &[u8]| -> anyhow::Result<()> {
let parsed = s.parse::<B16>()?;
assert_eq!(parsed.as_bytes(), b, "{s:?}");
assert_eq!(parsed.to_string(), s.to_ascii_lowercase());
let parsed = s.parse::<B16U>()?;
assert_eq!(parsed.as_bytes(), b, "{s:?}");
assert_eq!(parsed.to_string(), s.to_ascii_uppercase());
Ok(())
};
chk("332e313432", b"3.142")?;
chk("332E313432", b"3.142")?;
chk("332E3134", b"3.14")?;
assert!("332E313".parse::<B16>().is_err());
assert!("332G3134".parse::<B16>().is_err());
Ok(())
}
#[test]
fn curve25519() -> Result<()> {
use tor_llcrypto::pk::curve25519::PublicKey;
let k1 = "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz8=";
let k2 = hex::decode("a69c2d8475d6f245c3d1ff5f13b50f62c38002ee2e8f9391c12a2608cc4a933f")
.unwrap();
let k2: &[u8; 32] = &k2[..].try_into().unwrap();
let k1: PublicKey = k1.parse::<Curve25519Public>()?.into();
assert_eq!(k1, (*k2).into());
assert!(
"ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz"
.parse::<Curve25519Public>()
.is_err()
);
assert!(
"ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORSomCMxKkz"
.parse::<Curve25519Public>()
.is_err()
);
assert!(
"ppwthHXW8kXD0f9fE7UPYsOAAu4uj5wSomCMxKkz"
.parse::<Curve25519Public>()
.is_err()
);
assert!(
"ppwthHXW8kXD0f9fE7UPYsOAAu4ORwSomCMxKkz"
.parse::<Curve25519Public>()
.is_err()
);
Ok(())
}
#[test]
fn ed25519() -> Result<()> {
use tor_llcrypto::pk::ed25519::Ed25519Identity;
let k1 = "WVIPQ8oArAqLY4XzkcpIOI6U8KsUJHBQhG8SC57qru0";
let k2 = hex::decode("59520f43ca00ac0a8b6385f391ca48388e94f0ab14247050846f120b9eeaaeed")
.unwrap();
let k1: Ed25519Identity = k1.parse::<Ed25519Public>()?.into();
assert_eq!(k1, Ed25519Identity::from_bytes(&k2).unwrap());
assert!(
"WVIPQ8oArAqLY4Xzk0!!!!8KsUJHBQhG8SC57qru"
.parse::<Ed25519Public>()
.is_err()
);
assert!(
"WVIPQ8oArAqLY4XzkcpIU8KsUJHBQhG8SC57qru"
.parse::<Ed25519Public>()
.is_err()
);
assert!(
"WVIPQ8oArAqLY4XzkcpIU8KsUJHBQhG8SC57qr"
.parse::<Ed25519Public>()
.is_err()
);
assert!(
"ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxaaaa"
.parse::<Curve25519Public>()
.is_err()
);
Ok(())
}
#[test]
fn time() -> Result<()> {
use humantime::parse_rfc3339;
use std::time::SystemTime;
let t = "2020-09-29 13:36:33".parse::<Iso8601TimeSp>()?;
let t: SystemTime = t.into();
assert_eq!(t, parse_rfc3339("2020-09-29T13:36:33Z").unwrap());
assert!("2020-FF-29 13:36:33".parse::<Iso8601TimeSp>().is_err());
assert!("2020-09-29Q13:99:33".parse::<Iso8601TimeSp>().is_err());
assert!("2020-09-29".parse::<Iso8601TimeSp>().is_err());
assert!("too bad, waluigi time".parse::<Iso8601TimeSp>().is_err());
assert_eq!(
"2020-09-29 13:36:33",
"2020-09-29 13:36:33".parse::<Iso8601TimeSp>()?.to_string()
);
let t = "2020-09-29T13:36:33".parse::<Iso8601TimeNoSp>()?;
let t: SystemTime = t.into();
assert_eq!(t, parse_rfc3339("2020-09-29T13:36:33Z").unwrap());
assert!("2020-09-29 13:36:33".parse::<Iso8601TimeNoSp>().is_err());
assert!("2020-09-29Q13:99:33".parse::<Iso8601TimeNoSp>().is_err());
assert!("2020-09-29".parse::<Iso8601TimeNoSp>().is_err());
assert!("too bad, waluigi time".parse::<Iso8601TimeNoSp>().is_err());
assert_eq!(
"2020-09-29T13:36:33",
"2020-09-29T13:36:33"
.parse::<Iso8601TimeNoSp>()?
.to_string()
);
Ok(())
}
#[test]
fn rsa_public_key() {
let key_b64 = r#"
MIIBigKCAYEAsDkzTcKS4kAF56R2ijb9qCek53tKC1EwMdpWMk58bB28fY6kHc55
E7n1hB+LC5neZlx88GKuZ9k8P3g0MlO5ejalcfBdIIm28Nz86JXf/L23YnEpxnG/
IpxZEcmx/EYN+vwp72W3DGuzyntaoaut6lGJk+O/aRCLLcTm4MNznvN1ackK2H6b
Xm2ejRwtVRLoPKODJiPGl43snCfXXWsMH3IALFOgm0szPLv2fAJzBI8VWrUN81M/
lgwJhG6+xbr1CkrXI5fKs/TNr0B0ydC9BIZplmPrnXaeNklnw1cqUJ1oxDSgBrvx
rpDo7paObjSPV26opa68QKGa7Gu2MZQC3RzViNCbawka/108g6hSUkoM+Om2oivr
DvtMOs10MjsfibEBVnwEhqnlb/gj3hJkYoGRsCwAyMIaMObHcmAevMJRWAjGCc8T
GMS9dSmg1IZst+U+V2OCcIHXT6wZ1zPsBM0pYKVLCwtewaq1306k0n+ekriEo7eI
FS3Dd/Dx/a6jAgMBAAE=
"#;
let key_bytes = base64_decode_ignore_ws(key_b64).unwrap();
let rsa = RsaPublicParse1Helper::from_vec(key_bytes, Pos::None).unwrap();
let bits = tor_llcrypto::pk::rsa::PublicKey::from(rsa.clone()).bits();
assert_eq!(bits, 3072);
assert!(rsa.clone().check_exponent(65537).is_ok());
assert!(rsa.clone().check_exponent(1337).is_err());
assert!(rsa.clone().check_len_eq(3072).is_ok());
assert!(rsa.clone().check_len(1024..=4096).is_ok());
assert!(rsa.clone().check_len(1024..=1024).is_err());
assert!(rsa.check_len(4096..).is_err());
let failure = RsaPublicParse1Helper::from_vec(vec![1, 2, 3], Pos::None);
assert!(failure.is_err());
}
#[test]
fn ed_cert() {
use tor_llcrypto::pk::ed25519::Ed25519Identity;
let cert_b64 = r#"
AQQABwRNAR6m3kq5h8i3wwac+Ti293opoOP8RKGP9MT0WD4Bbz7YAQAgBACGCdys
G7AwsoYMIKenDN6In6ReiGF8jaYoGqmWKDVBdGGMDIZyNIq+VdhgtAB1EyNFHJU1
jGM0ir9dackL+PIsHbzJH8s/P/8RfUsKIL6/ZHbn3nKMxLH/8kjtxp5ScAA=
"#;
let cert_bytes = base64_decode_ignore_ws(cert_b64).unwrap();
let right_subject_key: Ed25519Identity = "HqbeSrmHyLfDBpz5OLb3eimg4/xEoY/0xPRYPgFvPtg"
.parse::<Ed25519Public>()
.unwrap()
.into();
let wrong_subject_key: Ed25519Identity = "WVIPQ8oArAqLY4XzkcpIOI6U8KsUJHBQhG8SC57qru0"
.parse::<Ed25519Public>()
.unwrap()
.into();
let cert = UnvalidatedEdCert::from_vec(cert_bytes, Pos::None)
.unwrap()
.check_cert_type(tor_cert::CertType::IDENTITY_V_SIGNING)
.unwrap()
.check_subject_key_is(&right_subject_key)
.unwrap();
assert!(
cert.clone()
.check_cert_type(tor_cert::CertType::RSA_ID_X509)
.is_err()
);
assert!(cert.check_subject_key_is(&wrong_subject_key).is_err());
let failure = UnvalidatedEdCert::from_vec(vec![1, 2, 3], Pos::None);
assert!(failure.is_err());
}
#[test]
fn fingerprint() -> Result<()> {
use tor_llcrypto::pk::rsa::RsaIdentity;
let fp1 = "7467 A97D 19CD 2B4F 2BC0 388A A99C 5E67 710F 847E";
let fp2 = "7467A97D19CD2B4F2BC0388AA99C5E67710F847E";
let fp3 = "$7467A97D19CD2B4F2BC0388AA99C5E67710F847E";
let fp4 = "$7467A97D19CD2B4F2BC0388AA99C5E67710F847E=fred";
let k = hex::decode(fp2).unwrap();
let k = RsaIdentity::from_bytes(&k[..]).unwrap();
assert_eq!(RsaIdentity::from(fp1.parse::<SpFingerprint>()?), k);
assert_eq!(RsaIdentity::from(fp2.parse::<SpFingerprint>()?), k);
assert!(fp3.parse::<SpFingerprint>().is_err());
assert!(fp4.parse::<SpFingerprint>().is_err());
assert!(fp1.parse::<Fingerprint>().is_err());
assert_eq!(RsaIdentity::from(fp2.parse::<Fingerprint>()?), k);
assert!(fp3.parse::<Fingerprint>().is_err());
assert!(fp4.parse::<Fingerprint>().is_err());
assert_eq!(Fingerprint(k).to_string(), fp2);
assert!(fp1.parse::<LongIdent>().is_err());
assert_eq!(RsaIdentity::from(fp2.parse::<LongIdent>()?), k);
assert_eq!(RsaIdentity::from(fp3.parse::<LongIdent>()?), k);
assert_eq!(RsaIdentity::from(fp4.parse::<LongIdent>()?), k);
assert!("xxxx".parse::<Fingerprint>().is_err());
assert!("ffffffffff".parse::<Fingerprint>().is_err());
let fp_b64 = "dGepfRnNK08rwDiKqZxeZ3EPhH4";
assert_eq!(RsaIdentity::from(fp_b64.parse::<Base64Fingerprint>()?), k);
assert_eq!(Base64Fingerprint(k).to_string(), fp_b64);
Ok(())
}
#[test]
fn nickname() -> anyhow::Result<()> {
let n: Nickname = "Foo".parse()?;
assert_eq!(n.as_str(), "Foo");
assert_eq!(n.to_string(), "Foo");
let word = "Untr1gonometr1cally";
assert_eq!(word.len(), 19);
let long: Nickname = word.parse()?;
assert_eq!(long.as_str(), word);
let too_long = "abcdefghijklmnopqrstuvwxyz";
let not_ascii = "Eyjafjallajökull";
let too_short = "";
let other_invalid = "contains space";
assert!(not_ascii.len() <= 19);
assert!(too_long.parse::<Nickname>().is_err());
assert!(not_ascii.parse::<Nickname>().is_err());
assert!(too_short.parse::<Nickname>().is_err());
assert!(other_invalid.parse::<Nickname>().is_err());
Ok(())
}
#[test]
fn hostname() {
use std::net::IpAddr;
let chk_name = |s: &str| {
let n: Hostname = s.parse().expect(s);
assert_eq!(n.as_str(), s);
assert_eq!(n.to_string(), s);
assert_eq!(s.parse::<InternetHost>().expect(s), InternetHost::Name(n));
};
let chk_either = |s: &str| {
let h: InternetHost = s.parse().expect(s);
let a: IpAddr = s.parse().expect(s);
assert_eq!(h, InternetHost::IpAddr(a), "{s:?}");
assert_eq!(h.to_string(), a.to_string(), "{s:?}");
};
let chk_addr = |s: &str| {
let _: InvalidHostname = s.parse::<Hostname>().expect_err(s);
chk_either(s);
};
let chk_bad = |s: &str| {
let _: InvalidHostname = s.parse::<Hostname>().expect_err(s);
let _: InvalidInternetHost = s.parse::<InternetHost>().expect_err(s);
};
chk_name("foo.bar");
chk_name("localhost");
chk_name("tor.invalid");
chk_name("example.com");
chk_bad("");
chk_bad("foo bar");
chk_bad("foo..bar");
chk_bad("foo.-bar");
chk_bad(" foo.bar ");
chk_bad("[::1]");
chk_bad("1");
chk_bad("127.0.0.023");
chk_bad("1.2.3.4.5");
chk_either("0.0.0.0");
chk_either("127.0.0.1");
chk_addr("::");
chk_addr("::1");
chk_addr("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
chk_addr("::ffff:192.0.2.3"); }
#[test]
fn contact_info() -> anyhow::Result<()> {
use encode::NetdocEncodable;
use parse2::{ParseInput, parse_netdoc};
const S: &str = "some relay operator";
let n: ContactInfo = S.parse()?;
assert_eq!(n.as_str(), S);
assert_eq!(n.to_string(), S);
let bad = |s: &str| {
let _: InvalidContactInfo = s.parse::<ContactInfo>().unwrap_err();
};
bad(" starts with space");
bad("contains\nnewline");
#[derive(PartialEq, Debug, Deftly)]
#[derive_deftly(NetdocParseable, NetdocEncodable)]
struct TestDoc {
pub intro: (),
pub contact: ContactInfo,
}
let roundtrip = |s: &str| -> anyhow::Result<()> {
let doc = TestDoc {
intro: (),
contact: s.parse()?,
};
let mut enc = NetdocEncoder::new();
doc.encode_unsigned(&mut enc)?;
let enc = enc.finish()?;
let reparsed = parse_netdoc::<TestDoc>(&ParseInput::new(&enc, "<test>"))?;
assert_eq!(doc, reparsed);
Ok(())
};
roundtrip("normal")?;
roundtrip("trailing white space ")?;
roundtrip("wtf is this allowed in \x03 netdocs\r")?;
Ok(())
}
#[test]
fn numeric_boolean() {
let chk = |s: &str| {
assert_eq!(NumericBoolean::from_str(s).expect(s).to_string(), s);
};
chk("0");
chk("1");
assert!(NumericBoolean::from_str("10000").is_err());
}
#[test]
fn sp_fingerprint() {
use derive_deftly::Deftly;
use tor_llcrypto::pk::rsa::RsaIdentity;
use crate::parse2::ErrorProblem;
#[derive(Deftly)]
#[derive_deftly(NetdocParseable)]
struct Wrapper {
#[deftly(netdoc(single_arg))]
fingerprint: SpFingerprint,
}
fn parse2(s: &str) -> std::result::Result<SpFingerprint, ErrorProblem> {
use crate::parse2::{self, ParseInput};
let s = format!("fingerprint {s}\n");
parse2::parse_netdoc::<Wrapper>(&ParseInput::new(&s, ""))
.map(|x| x.fingerprint)
.map_err(|x| x.problem)
}
assert_eq!(
parse2(&vec!["ABAB"; 10].join(" ")).unwrap(),
SpFingerprint(RsaIdentity::from_bytes(&[0xAB; 20]).unwrap())
);
assert_eq!(
parse2(&vec!["ABAB"; 11].join(" ")).unwrap(),
SpFingerprint(RsaIdentity::from_bytes(&[0xAB; 20]).unwrap())
);
assert!(matches!(
parse2(&vec!["ABAB"; 9].join(" ")).unwrap_err(),
ErrorProblem::MissingArgument { .. }
));
assert!(matches!(
parse2("0000 000000 00 0000 0000 0000 0000 0000 0000 0000").unwrap_err(),
ErrorProblem::InvalidArgument { .. }
));
assert!(matches!(
parse2(&vec!["ZZZZ"; 10].join(" ")).unwrap_err(),
ErrorProblem::InvalidArgument { .. }
));
}
}