diff --git a/crates/tor-netdoc/src/build.rs b/crates/tor-netdoc/src/build.rs index 30a1a238b..d29ea8705 100644 --- a/crates/tor-netdoc/src/build.rs +++ b/crates/tor-netdoc/src/build.rs @@ -24,10 +24,13 @@ use std::marker::PhantomData; use std::ops::Deref; use std::time::SystemTime; +use base64ct::{Base64, Encoding}; use humantime::format_rfc3339; +use tor_bytes::EncodeError; use tor_error::{internal, into_internal, Bug}; use crate::parse::keyword::Keyword; +use crate::parse::tokenize::tag_keywords_ok; /// Network document text according to dir-spec.txt s1.2 and maybe s1.3 /// @@ -318,7 +321,33 @@ impl<'n> ItemEncoder<'n> { // Writeable isn't dyn-compatible data: impl tor_bytes::WriteableOnce, ) { - todo!() + use crate::parse::tokenize::object::*; + + self.doc.write_with(|out| { + if keywords.is_empty() || !tag_keywords_ok(keywords) { + return Err(internal!("bad object keywords string {:?}", keywords)); + } + let data = { + let mut bytes = vec![]; + data.write_into(&mut bytes) + .map_err(EncodeError::always_bug)?; + Base64::encode_string(&bytes) + }; + let mut data = &data[..]; + writeln!(out, "\n{BEGIN_STR}{keywords}{TAG_END}").expect("write!"); + while !data.is_empty() { + let (l, r) = if data.len() > BASE64_PEM_MAX_LINE { + data.split_at(BASE64_PEM_MAX_LINE) + } else { + (data, "") + }; + writeln!(out, "{l}").expect("write!"); + data = r; + } + // final newline will be written by Drop impl + write!(out, "{END_STR}{keywords}{TAG_END}").expect("write!"); + Ok(()) + }); } } @@ -341,6 +370,7 @@ mod test { //! use super::*; + use base64ct::{Base64Unpadded, Encoding}; use humantime::parse_rfc3339; fn time(s: &str) -> SystemTime { @@ -351,6 +381,15 @@ mod test { fn authcert() { use crate::doc::authcert::AuthCertKwd as ACK; + // c&p from crates/tor-llcrypto/tests/testvec.rs + let pk_rsa = { + let pem = " +MIGJAoGBANUntsY9boHTnDKKlM4VfczcBE6xrYwhDJyeIkh7TPrebUBBvRBGmmV+ +PYK8AM9irDtqmSR+VztUwQxH9dyEmwrM2gMeym9uXchWd/dt7En/JNL8srWIf7El +qiBHRBGbtkF/Re5pb438HC/CGyuujp43oZ3CUYosJOfY/X+sD0aVAgMBAAE"; + Base64Unpadded::decode_vec(&pem.replace('\n', "")).unwrap() + }; + let mut encode = NetdocEncoder::new(); encode.item(ACK::DIR_KEY_CERTIFICATE_VERSION).arg(&3); encode @@ -362,6 +401,10 @@ mod test { encode .item(ACK::DIR_KEY_EXPIRES) .arg(&time("2021-04-18T08:36:57Z")); + encode + .item(ACK::DIR_IDENTITY_KEY) + .object("RSA PUBLIC KEY", &*pk_rsa); + let doc = encode.finish().unwrap(); eprintln!("{}", &*doc); assert_eq!( @@ -370,6 +413,12 @@ mod test { fingerprint ED03BB616EB2F60BEC80151114BB25CEF515B226 dir-key-published 2020-04-18 08:36:57 dir-key-expires 2021-04-18 08:36:57 +dir-identity-key +-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBANUntsY9boHTnDKKlM4VfczcBE6xrYwhDJyeIkh7TPrebUBBvRBGmmV+ +PYK8AM9irDtqmSR+VztUwQxH9dyEmwrM2gMeym9uXchWd/dt7En/JNL8srWIf7El +qiBHRBGbtkF/Re5pb438HC/CGyuujp43oZ3CUYosJOfY/X+sD0aVAgMBAAE= +-----END RSA PUBLIC KEY----- " ); } diff --git a/crates/tor-netdoc/src/parse/tokenize.rs b/crates/tor-netdoc/src/parse/tokenize.rs index 33c06d0a6..1afc080a9 100644 --- a/crates/tor-netdoc/src/parse/tokenize.rs +++ b/crates/tor-netdoc/src/parse/tokenize.rs @@ -22,7 +22,6 @@ pub(crate) mod object { /// indicates the end of a begin or end tag. pub(crate) const TAG_END: &str = "-----"; /// Maximum PEM base64 line length (not enforced during parsing) - #[allow(dead_code)] // TOOD HS pub(crate) const BASE64_PEM_MAX_LINE: usize = 64; }