diff --git a/src/base/opt/mod.rs b/src/base/opt/mod.rs index 244b6d92..58cca073 100644 --- a/src/base/opt/mod.rs +++ b/src/base/opt/mod.rs @@ -114,6 +114,9 @@ impl Opt<[u8]> { /// Checks that the slice contains acceptable OPT record data. fn check_slice(slice: &[u8]) -> Result<(), ParseError> { + if slice.len() > usize::from(u16::MAX) { + return Err(FormError::new("long record data").into()); + } let mut parser = Parser::from_ref(slice); while parser.remaining() > 0 { parser.advance(2)?; @@ -126,11 +129,15 @@ impl Opt<[u8]> { impl + ?Sized> Opt { /// Returns the length of the OPT record data. - #[allow(clippy::len_without_is_empty)] // never empty. pub fn len(&self) -> usize { self.octets.as_ref().len() } + /// Returns whether the OPT record data is empty. + pub fn is_empty(&self) -> bool { + self.octets.as_ref().is_empty() + } + /// Returns an iterator over options of a given type. /// /// The returned iterator will return only options represented by type diff --git a/src/base/rdata.rs b/src/base/rdata.rs index 399c21d1..aee58d88 100644 --- a/src/base/rdata.rs +++ b/src/base/rdata.rs @@ -243,11 +243,8 @@ impl UnknownRecordData { where Octs: AsRef<[u8]>, { - if data.as_ref().len() > 0xFFFF { - Err(LongRecordData()) - } else { - Ok(UnknownRecordData { rtype, data }) - } + LongRecordData::check_len(data.as_ref().len())?; + Ok(UnknownRecordData { rtype, data }) } /// Returns the record type this data is for. @@ -450,9 +447,23 @@ impl> fmt::Debug for UnknownRecordData { #[derive(Clone, Copy, Debug)] pub struct LongRecordData(); +impl LongRecordData { + pub fn as_str(self) -> &'static str { + "record data too long" + } + + pub fn check_len(len: usize) -> Result<(), Self> { + if len > usize::from(u16::MAX) { + Err(LongRecordData()) + } else { + Ok(()) + } + } +} + impl fmt::Display for LongRecordData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("record data too long") + f.write_str(self.as_str()) } } diff --git a/src/rdata/macros.rs b/src/rdata/macros.rs index 957d4321..fb616cbb 100644 --- a/src/rdata/macros.rs +++ b/src/rdata/macros.rs @@ -75,7 +75,7 @@ macro_rules! rdata_types { Unknown($crate::base::rdata::UnknownRecordData), } - impl, Name> ZoneRecordData { + impl, Name: ToDname> ZoneRecordData { /// Scans a value of the given rtype. /// /// If the record data is given via the notation for unknown diff --git a/src/rdata/rfc1035.rs b/src/rdata/rfc1035.rs index 6807fb6f..b42ed443 100644 --- a/src/rdata/rfc1035.rs +++ b/src/rdata/rfc1035.rs @@ -9,14 +9,16 @@ use crate::base::cmp::CanonicalOrd; use crate::base::iana::Rtype; use crate::base::name::{Dname, ParsedDname, PushError, ToDname}; use crate::base::net::Ipv4Addr; -use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData}; +use crate::base::rdata::{ + ComposeRecordData, LongRecordData, ParseRecordData, RecordData +}; use crate::base::scan::{Scan, Scanner, ScannerError, Symbol}; use crate::base::serial::Serial; -use crate::base::wire::{Compose, Composer, Parse, ParseError}; +use crate::base::wire::{Compose, Composer, FormError, Parse, ParseError}; #[cfg(feature = "bytes")] use bytes::BytesMut; use core::cmp::Ordering; -use core::convert::Infallible; +use core::convert::{Infallible, TryFrom}; use core::str::FromStr; use core::{fmt, hash, ops, str}; use octseq::builder::{ @@ -937,7 +939,7 @@ dname_type_well_known! { /// The Null record type is defined in RFC 1035, section 3.3.10. #[derive(Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Null { +pub struct Null { #[cfg_attr( feature = "serde", serde( @@ -953,21 +955,54 @@ pub struct Null { } impl Null { - /// Creates new, empty owned Null record data. - pub fn new(data: Octs) -> Self { - Null { data } + /// Creates new NULL record data from the given octets. + /// + /// The function will fail if `data` is longer than 65,535 octets. + pub fn from_octets(data: Octs) -> Result + where Octs: AsRef<[u8]> { + Null::check_slice(data.as_ref())?; + Ok(unsafe { Self::from_octets_unchecked(data) }) } + /// Creates new NULL record data without checking. + /// + /// # Safety + /// + /// The caller has to ensure that `data` is at most 65,535 octets long. + pub unsafe fn from_octets_unchecked(data: Octs) -> Self { + Null { data } + } +} + +impl Null<[u8]> { + /// Creates new NULL record data from an octets slice. + /// + /// The function will fail if `data` is longer than 65,535 octets. + pub fn from_slice(data: &[u8]) -> Result<&Self, LongRecordData> { + Self::check_slice(data)?; + Ok(unsafe { Self::from_slice_unchecked(data) }) + } + + /// Creates new NULL record from an octets slice data without checking. + /// + /// # Safety + /// + /// The caller has to ensure that `data` is at most 65,535 octets long. + pub unsafe fn from_slice_unchecked(data: &[u8]) -> &Self { + &*(data as *const [u8] as *const Self) + } + + /// Checks that a slice can be used for NULL record data. + fn check_slice(slice: &[u8]) -> Result<(), LongRecordData> { + LongRecordData::check_len(slice.len()) + } +} + +impl Null { /// The raw content of the record. pub fn data(&self) -> &Octs { &self.data } - - pub(super) fn convert_octets>( - self, - ) -> Result, Target::Error> { - Ok(Null::new(self.data.try_octets_into()?)) - } } impl> Null { @@ -980,12 +1015,24 @@ impl> Null { } } +impl Null { + pub(super) fn convert_octets>( + self, + ) -> Result, Target::Error> { + Ok(unsafe { + Null::from_octets_unchecked(self.data.try_octets_into()?) + }) + } +} + impl Null { pub fn parse<'a, Src: Octets = Octs> + ?Sized>( parser: &mut Parser<'a, Src>, ) -> Result { let len = parser.remaining(); - parser.parse_octets(len).map(Self::new).map_err(Into::into) + parser.parse_octets(len).map(|res| { + unsafe { Self::from_octets_unchecked(res) } + }).map_err(Into::into) } } @@ -994,15 +1041,11 @@ impl Null { where Octs: OctetsFrom, { - Ok(Null::new(self.data.try_octets_into().map_err(Into::into)?)) - } -} - -//--- From - -impl From for Null { - fn from(data: Octs) -> Self { - Self::new(data) + Ok(unsafe { + Null::from_octets_unchecked( + self.data.try_octets_into().map_err(Into::into)? + ) + }) } } @@ -1015,7 +1058,9 @@ where type Error = Octs::Error; fn try_octets_from(source: Null) -> Result { - Octs::try_octets_from(source.data).map(Self::new) + Octs::try_octets_from(source.data).map(|res| { + unsafe { Self::from_octets_unchecked(res) } + }) } } @@ -1023,22 +1068,22 @@ where impl PartialEq> for Null where - Octs: AsRef<[u8]>, - Other: AsRef<[u8]>, + Octs: AsRef<[u8]> + ?Sized, + Other: AsRef<[u8]> + ?Sized, { fn eq(&self, other: &Null) -> bool { self.data.as_ref().eq(other.data.as_ref()) } } -impl> Eq for Null {} +impl + ?Sized> Eq for Null {} //--- PartialOrd, CanonicalOrd, and Ord impl PartialOrd> for Null where - Octs: AsRef<[u8]>, - Other: AsRef<[u8]>, + Octs: AsRef<[u8]> + ?Sized, + Other: AsRef<[u8]> + ?Sized, { fn partial_cmp(&self, other: &Null) -> Option { self.data.as_ref().partial_cmp(other.data.as_ref()) @@ -1047,15 +1092,15 @@ where impl CanonicalOrd> for Null where - Octs: AsRef<[u8]>, - Other: AsRef<[u8]>, + Octs: AsRef<[u8]> + ?Sized, + Other: AsRef<[u8]> + ?Sized, { fn canonical_cmp(&self, other: &Null) -> Ordering { self.data.as_ref().cmp(other.data.as_ref()) } } -impl> Ord for Null { +impl + ?Sized> Ord for Null { fn cmp(&self, other: &Self) -> Ordering { self.data.as_ref().cmp(other.data.as_ref()) } @@ -1063,7 +1108,7 @@ impl> Ord for Null { //--- Hash -impl> hash::Hash for Null { +impl + ?Sized> hash::Hash for Null { fn hash(&self, state: &mut H) { self.data.as_ref().hash(state) } @@ -1071,7 +1116,7 @@ impl> hash::Hash for Null { //--- RecordData, ParseRecordData, ComposeRecordData -impl RecordData for Null { +impl RecordData for Null { fn rtype(&self) -> Rtype { Rtype::Null } @@ -1093,7 +1138,7 @@ where } } -impl> ComposeRecordData for Null { +impl + ?Sized> ComposeRecordData for Null { fn rdlen(&self, _compress: bool) -> Option { Some( u16::try_from(self.data.as_ref().len()).expect("long NULL rdata"), @@ -1115,16 +1160,6 @@ impl> ComposeRecordData for Null { } } -//--- Deref - -impl ops::Deref for Null { - type Target = Octs; - - fn deref(&self) -> &Self::Target { - &self.data - } -} - //--- AsRef impl, Other> AsRef for Null { @@ -1570,7 +1605,7 @@ impl Txt { impl Txt { /// Creates new TXT record data from its encoded content. - pub fn from_octets(octets: Octs) -> Result + pub fn from_octets(octets: Octs) -> Result where Octs: AsRef<[u8]> { @@ -1587,7 +1622,40 @@ impl Txt { unsafe fn from_octets_unchecked(octets: Octs) -> Self { Txt(octets) } +} +impl Txt<[u8]> { + /// Creates new TXT record data on an octets slice. + pub fn from_slice(slice: &[u8]) -> Result<&Self, TxtError> { + Txt::check_slice(slice)?; + Ok(unsafe { Txt::from_slice_unchecked(slice) }) + } + + /// Creates new TXT record data on an octets slice without checking. + /// + /// # Safety + /// + /// The passed octets must contain correctly encoded TXT record data, + /// that is a sequence of encoded character strings. + unsafe fn from_slice_unchecked(slice: &[u8]) -> &Self { + unsafe { &*(slice as *const [u8] as *const Self) } + } + + /// Checks that a slice contains correctly encoded TXT data. + fn check_slice(mut slice: &[u8]) -> Result<(), TxtError> { + LongRecordData::check_len(slice.len())?; + while let Some(&len) = slice.first() { + let len = usize::from(len); + if slice.len() <= len { + return Err(TxtError(TxtErrorInner::ShortInput)); + } + slice = &slice[len + 1..]; + } + Ok(()) + } +} + +impl Txt { pub fn parse<'a, Src: Octets = Octs> + ?Sized>( parser: &mut Parser<'a, Src>, ) -> Result @@ -1608,36 +1676,6 @@ impl Txt { } } -impl Txt<[u8]> { - /// Creates new TXT record data on an octets slice. - pub fn from_slice(slice: &[u8]) -> Result<&Self, CharStrError> { - Txt::check_slice(slice)?; - Ok(unsafe { Txt::from_slice_unchecked(slice) }) - } - - /// Creates new TXT record data on an octets slice without checking. - /// - /// # Safety - /// - /// The passed octets must contain correctly encoded TXT record data, - /// that is a sequence of encoded character strings. - unsafe fn from_slice_unchecked(slice: &[u8]) -> &Self { - unsafe { &*(slice as *const [u8] as *const Self) } - } - - /// Checks that a slice contains correctly encoded TXT data. - fn check_slice(mut slice: &[u8]) -> Result<(), CharStrError> { - while let Some(&len) = slice.first() { - let len = usize::from(len); - if slice.len() <= len { - return Err(CharStrError); - } - slice = &slice[len + 1..]; - } - Ok(()) - } -} - impl + ?Sized> Txt { /// Returns an iterator over the text items. /// @@ -2176,7 +2214,48 @@ impl Default for TxtBuilder { } } -//============ Testing ====================================================== +//============ Error Types =================================================== + +//------------ TxtError ------------------------------------------------------ + +/// An octets sequence does not form valid TXT record data. +#[derive(Clone, Copy, Debug)] +pub struct TxtError(TxtErrorInner); + +#[derive(Clone, Copy, Debug)] +enum TxtErrorInner { + Long(LongRecordData), + ShortInput, +} + +impl TxtError { + pub fn as_str(self) -> &'static str { + match self.0 { + TxtErrorInner::Long(err) => err.as_str(), + TxtErrorInner::ShortInput => "short input", + } + } +} + +impl From for TxtError { + fn from(err: LongRecordData) -> TxtError { + TxtError(TxtErrorInner::Long(err)) + } +} + +impl From for FormError { + fn from(err: TxtError) -> FormError { + FormError::new(err.as_str()) + } +} + +impl fmt::Display for TxtError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +//============ Testing ======================================================= #[cfg(test)] #[cfg(all(feature = "std", feature = "bytes"))] @@ -2276,7 +2355,7 @@ mod test { #[test] fn null_compose_parse_scan() { - let rdata = Null::new("foo"); + let rdata = Null::from_octets("foo").unwrap(); test_rdlen(&rdata); test_compose_parse(&rdata, |parser| Null::parse(parser)); } diff --git a/src/rdata/rfc2845.rs b/src/rdata/rfc2845.rs index 0f302fca..94ccdd0d 100644 --- a/src/rdata/rfc2845.rs +++ b/src/rdata/rfc2845.rs @@ -7,7 +7,9 @@ use crate::base::cmp::CanonicalOrd; use crate::base::iana::{Rtype, TsigRcode}; use crate::base::name::{Dname, ParsedDname, PushError, ToDname}; -use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData}; +use crate::base::rdata::{ + ComposeRecordData, LongRecordData, ParseRecordData, RecordData +}; use crate::base::wire::{Compose, Composer, Parse, ParseError}; use crate::utils::base64; use core::cmp::Ordering; @@ -77,14 +79,11 @@ pub struct Tsig { } impl Tsig { - /// Creates a new TSIG record from its components. + /// Creates new TSIG record data from its components. /// - /// See the access methods for an explanation of these components. - /// - /// # Panics - /// - /// Since `time_signed` is actually a 48 bit integer, the function will - /// panic of the upper 16 bits are not all 0. + /// See the access methods for an explanation of these components. The + /// function will return an error if the wire format length of the record + /// would exceed 65,535 octets. pub fn new( algorithm: N, time_signed: Time48, @@ -93,6 +92,42 @@ impl Tsig { original_id: u16, error: TsigRcode, other: O, + ) -> Result + where O: AsRef<[u8]>, N: ToDname { + LongRecordData::check_len( + 6 // time_signed + + 2 // fudge + + 2 // MAC length + + 2 // original ID + + 2 // error + + 2 // other length + + usize::from(algorithm.compose_len()).checked_add( + mac.as_ref().len() + ).expect("long MAC").checked_add( + other.as_ref().len() + ).expect("long TSIG") + )?; + Ok(unsafe { + Tsig::new_unchecked( + algorithm, time_signed, fudge, mac, original_id, error, other, + ) + }) + } + + /// Creates new TSIG record data without checking. + /// + /// # Safety + /// + /// The caller needs to ensure that the wire format length of the + /// created record will not exceed 65,535 octets. + pub unsafe fn new_unchecked( + algorithm: N, + time_signed: Time48, + fudge: u16, + mac: O, + original_id: u16, + error: TsigRcode, + other: O, ) -> Self { Tsig { algorithm, @@ -214,15 +249,17 @@ impl Tsig { TOcts: OctetsFrom, TName: OctetsFrom, { - Ok(Tsig::new( - self.algorithm.try_octets_into()?, - self.time_signed, - self.fudge, - self.mac.try_octets_into()?, - self.original_id, - self.error, - self.other.try_octets_into()?, - )) + Ok(unsafe { + Tsig::new_unchecked( + self.algorithm.try_octets_into()?, + self.time_signed, + self.fudge, + self.mac.try_octets_into()?, + self.original_id, + self.error, + self.other.try_octets_into()?, + ) + }) } } @@ -246,15 +283,17 @@ impl Tsig> { other, } = self; - Ok(Tsig::new( - algorithm.flatten_into()?, - time_signed, - fudge, - mac.try_octets_into().map_err(Into::into)?, - original_id, - error, - other.try_octets_into().map_err(Into::into)?, - )) + Ok(unsafe { + Tsig::new_unchecked( + algorithm.flatten_into()?, + time_signed, + fudge, + mac.try_octets_into().map_err(Into::into)?, + original_id, + error, + other.try_octets_into().map_err(Into::into)?, + ) + }) } } @@ -271,14 +310,10 @@ impl Tsig> { let error = TsigRcode::parse(parser)?; let other_len = u16::parse(parser)?; let other = parser.parse_octets(other_len as usize)?; - Ok(Tsig { - algorithm, - time_signed, - fudge, - mac, - original_id, - error, - other, + Ok(unsafe { + Tsig::new_unchecked( + algorithm, time_signed, fudge, mac, original_id, error, other, + ) }) } } @@ -297,15 +332,17 @@ where fn try_octets_from( source: Tsig, ) -> Result { - Ok(Tsig::new( - Name::try_octets_from(source.algorithm)?, - source.time_signed, - source.fudge, - Octs::try_octets_from(source.mac)?, - source.original_id, - source.error, - Octs::try_octets_from(source.other)?, - )) + Ok(unsafe { + Tsig::new_unchecked( + Name::try_octets_from(source.algorithm)?, + source.time_signed, + source.fudge, + Octs::try_octets_from(source.mac)?, + source.original_id, + source.error, + Octs::try_octets_from(source.other)?, + ) + }) } } @@ -666,7 +703,7 @@ impl fmt::Display for Time48 { } } -//============ Testing ====================================================== +//============ Testing ======================================================= #[cfg(test)] #[cfg(all(feature = "std", feature = "bytes"))] @@ -687,7 +724,7 @@ mod test { 13, TsigRcode::BadCookie, "", - ); + ).unwrap(); test_rdlen(&rdata); test_compose_parse(&rdata, |parser| Tsig::parse(parser)); } diff --git a/src/rdata/rfc4034.rs b/src/rdata/rfc4034.rs index 730bedf1..df4f2b9e 100644 --- a/src/rdata/rfc4034.rs +++ b/src/rdata/rfc4034.rs @@ -7,7 +7,9 @@ use crate::base::cmp::CanonicalOrd; use crate::base::iana::{DigestAlg, Rtype, SecAlg}; use crate::base::name::{Dname, ParsedDname, PushError, ToDname}; -use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData}; +use crate::base::rdata::{ + ComposeRecordData, LongRecordData, ParseRecordData, RecordData +}; use crate::base::scan::{Scan, Scanner, ScannerError}; use crate::base::serial::Serial; use crate::base::wire::{Compose, Composer, FormError, Parse, ParseError}; @@ -59,6 +61,36 @@ impl Dnskey { protocol: u8, algorithm: SecAlg, public_key: Octs, + ) -> Result + where + Octs: AsRef<[u8]>, + { + LongRecordData::check_len( + usize::from( + u16::COMPOSE_LEN + u8::COMPOSE_LEN + SecAlg::COMPOSE_LEN + ).checked_add( + public_key.as_ref().len() + ).expect("long key") + )?; + Ok(Dnskey { + flags, + protocol, + algorithm, + public_key, + }) + } + + /// Creates new DNSKEY record data without checking. + /// + /// # Safety + /// + /// The caller needs to ensure that wire format representation of the + /// record data is at most 65,535 octets long. + pub unsafe fn new_unchecked( + flags: u16, + protocol: u8, + algorithm: SecAlg, + public_key: Octs, ) -> Self { Dnskey { flags, @@ -176,12 +208,14 @@ impl Dnskey { pub(super) fn convert_octets>( self, ) -> Result, Target::Error> { - Ok(Dnskey::new( - self.flags, - self.protocol, - self.algorithm, - self.public_key.try_octets_into()?, - )) + Ok(unsafe { + Dnskey::new_unchecked( + self.flags, + self.protocol, + self.algorithm, + self.public_key.try_octets_into()?, + ) + }) } pub fn parse<'a, Src: Octets = Octs> + ?Sized>( @@ -191,23 +225,26 @@ impl Dnskey { Some(len) => len, None => return Err(ParseError::ShortInput), }; - Ok(Self::new( - u16::parse(parser)?, - u8::parse(parser)?, - SecAlg::parse(parser)?, - parser.parse_octets(len)?, - )) + Ok(unsafe { + Self::new_unchecked( + u16::parse(parser)?, + u8::parse(parser)?, + SecAlg::parse(parser)?, + parser.parse_octets(len)?, + ) + }) } pub fn scan>( scanner: &mut S, - ) -> Result { - Ok(Self::new( + ) -> Result + where Octs: AsRef<[u8]> { + Self::new( u16::scan(scanner)?, u8::scan(scanner)?, SecAlg::scan(scanner)?, scanner.convert_entry(base64::SymbolConverter::new())?, - )) + ).map_err(|err| S::Error::custom(err.as_str())) } } @@ -223,12 +260,14 @@ impl Dnskey { public_key, } = self; - Ok(Dnskey::new( - flags, - protocol, - algorithm, - public_key.try_octets_into().map_err(Into::into)?, - )) + Ok(unsafe { + Dnskey::new_unchecked( + flags, + protocol, + algorithm, + public_key.try_octets_into().map_err(Into::into)?, + ) + }) } } @@ -241,12 +280,14 @@ where type Error = Octs::Error; fn try_octets_from(source: Dnskey) -> Result { - Ok(Dnskey::new( - source.flags, - source.protocol, - source.algorithm, - Octs::try_octets_from(source.public_key)?, - )) + Ok(unsafe { + Dnskey::new_unchecked( + source.flags, + source.protocol, + source.algorithm, + Octs::try_octets_from(source.public_key)?, + ) + }) } } @@ -433,7 +474,10 @@ impl ProtoRrsig { } } - pub fn into_rrsig(self, signature: Octs) -> Rrsig { + pub fn into_rrsig>( + self, signature: Octs + ) -> Result, LongRecordData> + where Name: ToDname { Rrsig::new( self.type_covered, self.algorithm, @@ -595,6 +639,55 @@ impl Rrsig { key_tag: u16, signer_name: Name, signature: Octs, + ) -> Result + where Octs: AsRef<[u8]>, Name: ToDname { + LongRecordData::check_len( + usize::from( + Rtype::COMPOSE_LEN + + SecAlg::COMPOSE_LEN + + u8::COMPOSE_LEN + + u32::COMPOSE_LEN + + Serial::COMPOSE_LEN + + Serial::COMPOSE_LEN + + u16::COMPOSE_LEN + + signer_name.compose_len() + ).checked_add( + signature.as_ref().len() + ) + .expect("long signature") + )?; + Ok(unsafe { + Rrsig::new_unchecked( + type_covered, + algorithm, + labels, + original_ttl, + expiration, + inception, + key_tag, + signer_name, + signature, + ) + }) + } + + /// Creates new RRSIG record data without checking. + /// + /// # Safety + /// + /// The caller needs to ensure that wire format representation of the + /// record data is at most 65,535 octets long. + #[allow(clippy::too_many_arguments)] // XXX Consider changing. + pub unsafe fn new_unchecked( + type_covered: Rtype, + algorithm: SecAlg, + labels: u8, + original_ttl: u32, + expiration: Serial, + inception: Serial, + key_tag: u16, + signer_name: Name, + signature: Octs, ) -> Self { Rrsig { type_covered, @@ -656,23 +749,29 @@ impl Rrsig { TOcts: OctetsFrom, TName: OctetsFrom, { - Ok(Rrsig::new( - self.type_covered, - self.algorithm, - self.labels, - self.original_ttl, - self.expiration, - self.inception, - self.key_tag, - TName::try_octets_from(self.signer_name)?, - TOcts::try_octets_from(self.signature)?, - )) + Ok(unsafe { + Rrsig::new_unchecked( + self.type_covered, + self.algorithm, + self.labels, + self.original_ttl, + self.expiration, + self.inception, + self.key_tag, + TName::try_octets_from(self.signer_name)?, + TOcts::try_octets_from(self.signature)?, + ) + }) } pub fn scan>( scanner: &mut S, - ) -> Result { - Ok(Self::new( + ) -> Result + where + Octs: AsRef<[u8]>, + Name: ToDname, + { + Self::new( Rtype::scan(scanner)?, SecAlg::scan(scanner)?, u8::scan(scanner)?, @@ -682,7 +781,7 @@ impl Rrsig { u16::scan(scanner)?, scanner.scan_dname()?, scanner.convert_entry(base64::SymbolConverter::new())?, - )) + ).map_err(|err| S::Error::custom(err.as_str())) } } @@ -709,17 +808,19 @@ impl Rrsig> { signature, } = self; - Ok(Rrsig::new( - type_covered, - algorithm, - labels, - original_ttl, - expiration, - inception, - key_tag, - signer_name.flatten_into()?, - Target::try_octets_from(signature).map_err(Into::into)?, - )) + Ok(unsafe { + Rrsig::new_unchecked( + type_covered, + algorithm, + labels, + original_ttl, + expiration, + inception, + key_tag, + signer_name.flatten_into()?, + Target::try_octets_from(signature).map_err(Into::into)?, + ) + }) } } @@ -737,17 +838,19 @@ impl Rrsig> { let signer_name = ParsedDname::parse(parser)?; let len = parser.remaining(); let signature = parser.parse_octets(len)?; - Ok(Self::new( - type_covered, - algorithm, - labels, - original_ttl, - expiration, - inception, - key_tag, - signer_name, - signature, - )) + Ok(unsafe { + Self::new_unchecked( + type_covered, + algorithm, + labels, + original_ttl, + expiration, + inception, + key_tag, + signer_name, + signature, + ) + }) } } @@ -765,17 +868,19 @@ where fn try_octets_from( source: Rrsig, ) -> Result { - Ok(Rrsig::new( - source.type_covered, - source.algorithm, - source.labels, - source.original_ttl, - source.expiration, - source.inception, - source.key_tag, - Name::try_octets_from(source.signer_name)?, - Octs::try_octets_from(source.signature)?, - )) + Ok(unsafe { + Rrsig::new_unchecked( + source.type_covered, + source.algorithm, + source.labels, + source.original_ttl, + source.expiration, + source.inception, + source.key_tag, + Name::try_octets_from(source.signer_name)?, + Octs::try_octets_from(source.signature)?, + ) + }) } } @@ -1353,6 +1458,30 @@ impl Ds { algorithm: SecAlg, digest_type: DigestAlg, digest: Octs, + ) -> Result + where Octs: AsRef<[u8]> { + LongRecordData::check_len( + usize::from( + u16::COMPOSE_LEN + SecAlg::COMPOSE_LEN + DigestAlg::COMPOSE_LEN + ).checked_add(digest.as_ref().len()).expect("long digest") + )?; + Ok(unsafe { + Ds::new_unchecked(key_tag, algorithm, digest_type, digest) + }) + } + + + /// Creates new DS record data without checking. + /// + /// # Safety + /// + /// The caller needs to ensure that wire format representation of the + /// record data is at most 65,535 octets long. + pub unsafe fn new_unchecked( + key_tag: u16, + algorithm: SecAlg, + digest_type: DigestAlg, + digest: Octs, ) -> Self { Ds { key_tag, @@ -1385,12 +1514,14 @@ impl Ds { pub(super) fn convert_octets>( self, ) -> Result, Target::Error> { - Ok(Ds::new( - self.key_tag, - self.algorithm, - self.digest_type, - self.digest.try_octets_into()?, - )) + Ok(unsafe { + Ds::new_unchecked( + self.key_tag, + self.algorithm, + self.digest_type, + self.digest.try_octets_into()?, + ) + }) } pub fn parse<'a, Src: Octets = Octs> + ?Sized>( @@ -1400,23 +1531,26 @@ impl Ds { Some(len) => len, None => return Err(ParseError::ShortInput), }; - Ok(Self::new( - u16::parse(parser)?, - SecAlg::parse(parser)?, - DigestAlg::parse(parser)?, - parser.parse_octets(len)?, - )) + Ok(unsafe { + Self::new_unchecked( + u16::parse(parser)?, + SecAlg::parse(parser)?, + DigestAlg::parse(parser)?, + parser.parse_octets(len)?, + ) + }) } pub fn scan>( scanner: &mut S, - ) -> Result { - Ok(Self::new( + ) -> Result + where Octs: AsRef<[u8]> { + Self::new( u16::scan(scanner)?, SecAlg::scan(scanner)?, DigestAlg::scan(scanner)?, scanner.convert_entry(base16::SymbolConverter::new())?, - )) + ).map_err(|err| S::Error::custom(err.as_str())) } } @@ -1431,12 +1565,14 @@ impl Ds { digest_type, digest, } = self; - Ok(Ds::new( - key_tag, - algorithm, - digest_type, - digest.try_octets_into().map_err(Into::into)?, - )) + Ok(unsafe { + Ds::new_unchecked( + key_tag, + algorithm, + digest_type, + digest.try_octets_into().map_err(Into::into)?, + ) + }) } } @@ -1449,12 +1585,14 @@ where type Error = Octs::Error; fn try_octets_from(source: Ds) -> Result { - Ok(Ds::new( - source.key_tag, - source.algorithm, - source.digest_type, - Octs::try_octets_from(source.digest)?, - )) + Ok(unsafe { + Ds::new_unchecked( + source.key_tag, + source.algorithm, + source.digest_type, + Octs::try_octets_from(source.digest)?, + ) + }) } } @@ -2281,7 +2419,7 @@ mod test { #[test] fn dnskey_compose_parse_scan() { - let rdata = Dnskey::new(10, 11, SecAlg::RsaSha1, b"key"); + let rdata = Dnskey::new(10, 11, SecAlg::RsaSha1, b"key").unwrap(); test_rdlen(&rdata); test_compose_parse(&rdata, |parser| Dnskey::parse(parser)); test_scan(&["10", "11", "RSASHA1", "a2V5"], Dnskey::scan, &rdata); @@ -2301,7 +2439,7 @@ mod test { 15, Dname::>::from_str("example.com.").unwrap(), b"key", - ); + ).unwrap(); test_rdlen(&rdata); test_compose_parse(&rdata, |parser| Rrsig::parse(parser)); test_scan( @@ -2341,7 +2479,9 @@ mod test { #[test] fn ds_compose_parse_scan() { - let rdata = Ds::new(10, SecAlg::RsaSha1, DigestAlg::Sha256, b"key"); + let rdata = Ds::new( + 10, SecAlg::RsaSha1, DigestAlg::Sha256, b"key" + ).unwrap(); test_rdlen(&rdata); test_compose_parse(&rdata, |parser| Ds::parse(parser)); test_scan(&["10", "RSASHA1", "2", "6b6579"], Ds::scan, &rdata); @@ -2440,7 +2580,7 @@ mod test { KLZ02cRWXqM=" ) .unwrap() - ) + ).unwrap() .key_tag(), 59944 ); @@ -2460,7 +2600,7 @@ mod test { 9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU=" ) .unwrap() - ) + ).unwrap() .key_tag(), 20326 ); @@ -2476,7 +2616,7 @@ mod test { C+7Eoi12SqybMTicD3Ezwa9XbG1iPjmjhbMrLh7MSQpX" ) .unwrap() - ) + ).unwrap() .key_tag(), 18698 ); @@ -2484,8 +2624,9 @@ mod test { #[test] fn dnskey_flags() { - let dnskey = - Dnskey::new(257, 3, SecAlg::RsaSha256, bytes::Bytes::new()); + let dnskey = Dnskey::new( + 257, 3, SecAlg::RsaSha256, bytes::Bytes::new() + ).unwrap(); assert!(dnskey.is_zsk()); assert!(dnskey.is_secure_entry_point()); assert!(!dnskey.is_revoked()); diff --git a/src/rdata/rfc7344.rs b/src/rdata/rfc7344.rs index f53a6249..48ab035f 100644 --- a/src/rdata/rfc7344.rs +++ b/src/rdata/rfc7344.rs @@ -4,8 +4,10 @@ use crate::base::cmp::CanonicalOrd; use crate::base::iana::{DigestAlg, Rtype, SecAlg}; use crate::base::name::PushError; -use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData}; -use crate::base::scan::{Scan, Scanner}; +use crate::base::rdata::{ + ComposeRecordData, LongRecordData, ParseRecordData, RecordData +}; +use crate::base::scan::{Scan, Scanner, ScannerError}; use crate::base::wire::{Compose, Composer, Parse, ParseError}; use crate::utils::{base16, base64}; use core::cmp::Ordering; @@ -50,6 +52,29 @@ impl Cdnskey { protocol: u8, algorithm: SecAlg, public_key: Octs, + ) -> Result + where Octs: AsRef<[u8]> { + LongRecordData::check_len( + usize::from( + u16::COMPOSE_LEN + u8::COMPOSE_LEN + SecAlg::COMPOSE_LEN + ).checked_add(public_key.as_ref().len()).expect("long key") + )?; + Ok(unsafe { + Cdnskey::new_unchecked(flags, protocol, algorithm, public_key) + }) + } + + /// Creates new CDNSKEY record data without checking. + /// + /// # Safety + /// + /// The caller needs to ensure that wire format representation of the + /// record data is at most 65,535 octets long. + pub unsafe fn new_unchecked( + flags: u16, + protocol: u8, + algorithm: SecAlg, + public_key: Octs, ) -> Self { Cdnskey { flags, @@ -78,12 +103,14 @@ impl Cdnskey { pub(super) fn convert_octets>( self, ) -> Result, Target::Error> { - Ok(Cdnskey::new( - self.flags, - self.protocol, - self.algorithm, - self.public_key.try_octets_into()?, - )) + Ok(unsafe { + Cdnskey::new_unchecked( + self.flags, + self.protocol, + self.algorithm, + self.public_key.try_octets_into()?, + ) + }) } pub fn parse<'a, Src: Octets = Octs> + ?Sized>( @@ -93,23 +120,26 @@ impl Cdnskey { Some(len) => len, None => return Err(ParseError::ShortInput), }; - Ok(Self::new( - u16::parse(parser)?, - u8::parse(parser)?, - SecAlg::parse(parser)?, - parser.parse_octets(len)?, - )) + Ok(unsafe { + Self::new_unchecked( + u16::parse(parser)?, + u8::parse(parser)?, + SecAlg::parse(parser)?, + parser.parse_octets(len)?, + ) + }) } pub fn scan>( scanner: &mut S, - ) -> Result { - Ok(Self::new( + ) -> Result + where Octs: AsRef<[u8]> { + Self::new( u16::scan(scanner)?, u8::scan(scanner)?, SecAlg::scan(scanner)?, scanner.convert_entry(base64::SymbolConverter::new())?, - )) + ).map_err(|err| S::Error::custom(err.as_str())) } } @@ -124,12 +154,14 @@ impl Cdnskey { algorithm, public_key, } = self; - Ok(Cdnskey::new( - flags, - protocol, - algorithm, - public_key.try_octets_into().map_err(Into::into)?, - )) + Ok(unsafe { + Cdnskey::new_unchecked( + flags, + protocol, + algorithm, + public_key.try_octets_into().map_err(Into::into)?, + ) + }) } } @@ -144,12 +176,14 @@ where fn try_octets_from( source: Cdnskey, ) -> Result { - Ok(Cdnskey::new( - source.flags, - source.protocol, - source.algorithm, - Octs::try_octets_from(source.public_key)?, - )) + Ok(unsafe { + Cdnskey::new_unchecked( + source.flags, + source.protocol, + source.algorithm, + Octs::try_octets_from(source.public_key)?, + ) + }) } } @@ -334,6 +368,29 @@ impl Cds { algorithm: SecAlg, digest_type: DigestAlg, digest: Octs, + ) -> Result + where Octs: AsRef<[u8]> { + LongRecordData::check_len( + usize::from( + u16::COMPOSE_LEN + SecAlg::COMPOSE_LEN + DigestAlg::COMPOSE_LEN + ).checked_add(digest.as_ref().len()).expect("long digest") + )?; + Ok(unsafe { + Cds::new_unchecked(key_tag, algorithm, digest_type, digest) + }) + } + + /// Creates new CDS record data without checking. + /// + /// # Safety + /// + /// The caller needs to ensure that wire format representation of the + /// record data is at most 65,535 octets long. + pub unsafe fn new_unchecked( + key_tag: u16, + algorithm: SecAlg, + digest_type: DigestAlg, + digest: Octs, ) -> Self { Cds { key_tag, @@ -366,12 +423,14 @@ impl Cds { pub(super) fn convert_octets>( self, ) -> Result, Target::Error> { - Ok(Cds::new( - self.key_tag, - self.algorithm, - self.digest_type, - self.digest.try_octets_into()?, - )) + Ok(unsafe { + Cds::new_unchecked( + self.key_tag, + self.algorithm, + self.digest_type, + self.digest.try_octets_into()?, + ) + }) } pub fn parse<'a, Src: Octets = Octs> + ?Sized>( @@ -381,23 +440,26 @@ impl Cds { Some(len) => len, None => return Err(ParseError::ShortInput), }; - Ok(Self::new( - u16::parse(parser)?, - SecAlg::parse(parser)?, - DigestAlg::parse(parser)?, - parser.parse_octets(len)?, - )) + Ok(unsafe { + Self::new_unchecked( + u16::parse(parser)?, + SecAlg::parse(parser)?, + DigestAlg::parse(parser)?, + parser.parse_octets(len)?, + ) + }) } pub fn scan>( scanner: &mut S, - ) -> Result { - Ok(Self::new( + ) -> Result + where Octs: AsRef<[u8]> { + Self::new( u16::scan(scanner)?, SecAlg::scan(scanner)?, DigestAlg::scan(scanner)?, scanner.convert_entry(base16::SymbolConverter::new())?, - )) + ).map_err(|err| S::Error::custom(err.as_str())) } } @@ -406,18 +468,14 @@ impl Cds { where Octs: OctetsFrom, { - let Self { - key_tag, - algorithm, - digest_type, - digest, - } = self; - Ok(Cds::new( - key_tag, - algorithm, - digest_type, - digest.try_octets_into().map_err(Into::into)?, - )) + Ok(unsafe { + Cds::new_unchecked( + self.key_tag, + self.algorithm, + self.digest_type, + self.digest.try_octets_into().map_err(Into::into)?, + ) + }) } } @@ -430,12 +488,14 @@ where type Error = Octs::Error; fn try_octets_from(source: Cds) -> Result { - Ok(Cds::new( - source.key_tag, - source.algorithm, - source.digest_type, - Octs::try_octets_from(source.digest)?, - )) + Ok(unsafe { + Cds::new_unchecked( + source.key_tag, + source.algorithm, + source.digest_type, + Octs::try_octets_from(source.digest)?, + ) + }) } } @@ -623,7 +683,7 @@ mod test { #[test] fn cdnskey_compose_parse_scan() { - let rdata = Cdnskey::new(10, 11, SecAlg::RsaSha1, b"key"); + let rdata = Cdnskey::new(10, 11, SecAlg::RsaSha1, b"key").unwrap(); test_rdlen(&rdata); test_compose_parse(&rdata, |parser| Cdnskey::parse(parser)); test_scan(&["10", "11", "RSASHA1", "a2V5"], Cdnskey::scan, &rdata); @@ -633,7 +693,9 @@ mod test { #[test] fn cds_compose_parse_scan() { - let rdata = Cds::new(10, SecAlg::RsaSha1, DigestAlg::Sha256, b"key"); + let rdata = Cds::new( + 10, SecAlg::RsaSha1, DigestAlg::Sha256, b"key" + ).unwrap(); test_rdlen(&rdata); test_compose_parse(&rdata, |parser| Cds::parse(parser)); test_scan(&["10", "RSASHA1", "2", "6b6579"], Cds::scan, &rdata); diff --git a/src/sign/records.rs b/src/sign/records.rs index 8853968b..4fb20ede 100644 --- a/src/sign/records.rs +++ b/src/sign/records.rs @@ -74,7 +74,7 @@ impl SortedRecords { N: ToDname + Clone, D: RecordData + ComposeRecordData, Key: SigningKey, - Octets: From, + Octets: From + AsRef<[u8]>, ApexName: ToDname + Clone, { let mut res = Vec::new(); @@ -154,7 +154,9 @@ impl SortedRecords { name.owner().clone(), name.class(), rrset.ttl(), - rrsig.into_rrsig(key.sign(&buf)?.into()), + rrsig + .into_rrsig(key.sign(&buf)?.into()) + .expect("long signature"), )); } } diff --git a/src/sign/ring.rs b/src/sign/ring.rs index 97b1fd1a..1100d947 100644 --- a/src/sign/ring.rs +++ b/src/sign/ring.rs @@ -52,7 +52,8 @@ impl<'a> Key<'a> { 3, SecAlg::EcdsaP256Sha256, public_key, - ), + ) + .expect("long key"), key: RingKey::Ecdsa(keypair), rng, }) @@ -82,7 +83,8 @@ impl<'a> SigningKey for Key<'a> { self.dnskey.algorithm(), DigestAlg::Sha256, digest, - )) + ) + .expect("long digest")) } fn sign(&self, msg: &[u8]) -> Result { diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index af62fe1d..2d3e2c8c 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -1406,6 +1406,9 @@ impl Variables { key.name.clone(), Class::Any, 0, + // The only reason creating TSIG record data can fail here is + // that the hmac is unreasonable large. Since we control its + // creation, panicing in this case is fine. Tsig::new( key.algorithm().to_dname(), self.time_signed, @@ -1414,7 +1417,8 @@ impl Variables { original_id, self.error, other, - ), + ) + .expect("long MAC"), )) } @@ -1656,6 +1660,7 @@ impl> ServerError { tsig.owner(), tsig.class(), tsig.ttl(), + // The TSIG record data can never ever be to long. Tsig::new( tsig.data().algorithm(), tsig.data().time_signed(), @@ -1664,7 +1669,8 @@ impl> ServerError { msg.header().id(), error, b"", - ), + ) + .expect("long record data"), ))?; } ServerErrorInner::Signed { context, variables } => { diff --git a/src/validate.rs b/src/validate.rs index 0a5297da..d5edf7fa 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -382,8 +382,8 @@ mod test { ) .unwrap(); ( - Dnskey::new(257, 3, SecAlg::RsaSha256, ksk), - Dnskey::new(256, 3, SecAlg::RsaSha256, zsk), + Dnskey::new(257, 3, SecAlg::RsaSha256, ksk).unwrap(), + Dnskey::new(256, 3, SecAlg::RsaSha256, zsk).unwrap(), ) } @@ -398,8 +398,8 @@ mod test { ) .unwrap(); ( - Dnskey::new(257, 3, SecAlg::RsaSha256, ksk), - Dnskey::new(256, 3, SecAlg::RsaSha256, zsk), + Dnskey::new(257, 3, SecAlg::RsaSha256, ksk).unwrap(), + Dnskey::new(256, 3, SecAlg::RsaSha256, zsk).unwrap(), ) } @@ -415,7 +415,8 @@ mod test { "4G1EuAuPHTmpXAsNfGXQhFjogECbvGg0VxBCN8f47I0=", ) .unwrap(), - ); + ) + .unwrap(); assert_eq!( dnskey.digest(&owner, DigestAlg::Sha256).unwrap().as_ref(), expected.digest() @@ -474,7 +475,7 @@ mod test { "otBkINZAQu7AvPKjr/xWIEE7+SoZtKgF8bzVynX6bfJMJuPay8jPvNmwXkZOdSoYlvFp0bk9JWJKCh8y5uoNfMFkN6OSrDkr3t0E+c8c0Mnmwkk5CETH3Gqxthi0yyRX5T4VlHU06/Ks4zI+XAgl3FBpOc554ivdzez8YCjAIGx7XgzzooEb7heMSlLc7S7/HNjw51TPRs4RxrAVcezieKCzPPpeWBhjE6R3oiSwrl0SBD4/yplrDlr7UHs/Atcm3MSgemdyr2sOoOUkVQCVpcj3SQQezoD2tCM7861CXEQdg5fjeHDtz285xHt5HJpA5cOcctRo4ihybfow/+V7AQ==", ) .unwrap() - ); + ).unwrap(); rrsig_verify_dnskey(ksk, zsk, rrsig); // Test 1024b long key @@ -492,7 +493,7 @@ mod test { "j1s1IPMoZd0mbmelNVvcbYNe2tFCdLsLpNCnQ8xW6d91ujwPZ2yDlc3lU3hb+Jq3sPoj+5lVgB7fZzXQUQTPFWLF7zvW49da8pWuqzxFtg6EjXRBIWH5rpEhOcr+y3QolJcPOTx+/utCqt2tBKUUy3LfM6WgvopdSGaryWdwFJPW7qKHjyyLYxIGx5AEuLfzsA5XZf8CmpUheSRH99GRZoIB+sQzHuelWGMQ5A42DPvOVZFmTpIwiT2QaIpid4nJ7jNfahfwFrCoS+hvqjK9vktc5/6E/Mt7DwCQDaPt5cqDfYltUitQy+YA5YP5sOhINChYadZe+2N80OA+RKz0mA==", ) .unwrap() - ); + ).unwrap(); rrsig_verify_dnskey(ksk, zsk, rrsig.clone()); // Test that 512b short RSA DNSKEY is not supported (too short) @@ -501,7 +502,7 @@ mod test { ) .unwrap(); - let short_key = Dnskey::new(256, 3, SecAlg::RsaSha256, data); + let short_key = Dnskey::new(256, 3, SecAlg::RsaSha256, data).unwrap(); let err = rrsig .verify_signed_data(&short_key, &vec![0; 100]) .unwrap_err(); @@ -520,7 +521,8 @@ mod test { F+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==", ) .unwrap(), - ), + ) + .unwrap(), Dnskey::new( 256, 3, @@ -530,7 +532,8 @@ mod test { d8KqXXFJkqmVfRvMGPmM1x8fGAa2XhSA==", ) .unwrap(), - ), + ) + .unwrap(), ); let owner = Dname::from_str("cloudflare.com.").unwrap(); @@ -548,7 +551,8 @@ mod test { zcJBLvRmofYFDAhju21p1uTfLaYHrg==", ) .unwrap(), - ); + ) + .unwrap(); rrsig_verify_dnskey(ksk, zsk, rrsig); } @@ -563,7 +567,8 @@ mod test { "m1NELLVVQKl4fHVn/KKdeNO0PrYKGT3IGbYseT8XcKo=", ) .unwrap(), - ), + ) + .unwrap(), Dnskey::new( 256, 3, @@ -572,7 +577,8 @@ mod test { "2tstZAjgmlDTePn0NVXrAHBJmg84LoaFVxzLl1anjGI=", ) .unwrap(), - ), + ) + .unwrap(), ); let owner = @@ -592,7 +598,8 @@ mod test { QdtgPXja7YkTaqzrYUbYk01J8ICsAA==", ) .unwrap(), - ); + ) + .unwrap(); rrsig_verify_dnskey(ksk, zsk, rrsig); } @@ -617,7 +624,8 @@ mod test { dg5fjeHDtz285xHt5HJpA5cOcctRo4ihybfow/+V7AQ==", ) .unwrap(), - ); + ) + .unwrap(); let mut records: Vec, Dname>>> = [&ksk, &zsk] @@ -656,7 +664,8 @@ mod test { vh0z2542lzMKR4Dh8uZffQ==", ) .unwrap(), - ); + ) + .unwrap(); let rrsig = Rrsig::new( Rtype::Mx, SecAlg::RsaSha1, @@ -673,7 +682,8 @@ mod test { 36SR5xBni8vHI=", ) .unwrap(), - ); + ) + .unwrap(); let record = Record::new( Dname::from_str("a.z.w.example.").unwrap(), Class::In,