From fd715efaba6f7c4c8fd20310ba100e1f761be361 Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Mon, 13 Jul 2020 14:19:20 +0200 Subject: [PATCH 01/19] Parsing and building RTAs. --- src/cert/mod.rs | 7 + src/crl.rs | 7 + src/crypto/keys.rs | 11 + src/lib.rs | 1 + src/oid.rs | 2 + src/resources/asres.rs | 24 +- src/resources/ipres.rs | 16 +- src/roa.rs | 4 +- src/rta.rs | 852 +++++++++++++++++++++++++++++++++++++++++ src/sigobj.rs | 19 +- 10 files changed, 922 insertions(+), 21 deletions(-) create mode 100644 src/rta.rs diff --git a/src/cert/mod.rs b/src/cert/mod.rs index 530b7be0..3ca9688e 100644 --- a/src/cert/mod.rs +++ b/src/cert/mod.rs @@ -116,6 +116,13 @@ impl Cert { cons.take_sequence(Self::from_constructed) } + /// Takes an optional certificate from the beginning of a value. + pub fn take_opt_from( + cons: &mut decode::Constructed + ) -> Result, S::Err> { + cons.take_opt_sequence(Self::from_constructed) + } + /// Parses the content of a Certificate sequence. pub fn from_constructed( cons: &mut decode::Constructed diff --git a/src/crl.rs b/src/crl.rs index a38b4c48..f91d2969 100644 --- a/src/crl.rs +++ b/src/crl.rs @@ -102,6 +102,13 @@ impl Crl { cons.take_sequence(Self::from_constructed) } + /// Takes an encoded CRL from the beginning of a constructed value. + pub fn take_opt_from( + cons: &mut decode::Constructed + ) -> Result, S::Err> { + cons.take_opt_sequence(Self::from_constructed) + } + /// Parses the content of a certificate revocation list. pub fn from_constructed( cons: &mut decode::Constructed diff --git a/src/crypto/keys.rs b/src/crypto/keys.rs index 62992353..3ef0bb96 100644 --- a/src/crypto/keys.rs +++ b/src/crypto/keys.rs @@ -271,6 +271,17 @@ impl KeyIdentifier { Ok(res) } } + + /// Skips over an encoded key indentifier. + pub fn skip_in( + cons: &mut decode::Constructed + ) -> Result<(), S::Err> { + while cons.take_opt_value_if( + Tag::OCTET_STRING, OctetString::from_content + )?.is_some() { + } + Ok(()) + } } diff --git a/src/lib.rs b/src/lib.rs index fb9183a6..2c3291d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ pub mod oid; pub mod resources; pub mod roa; pub mod rrdp; +pub mod rta; pub mod sigobj; pub mod tal; pub mod uri; diff --git a/src/oid.rs b/src/oid.rs index 20345659..fc5f974c 100644 --- a/src/oid.rs +++ b/src/oid.rs @@ -63,6 +63,8 @@ pub const CP_IPADDR_ASNUMBER_V2: Oid<&[u8]> = Oid(&[43, 6, 1, 5, 5, 7, 14, 3]); pub const CT_RPKI_MANIFEST: ConstOid = Oid(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 1, 26]); +pub const CT_RESOURCE_TAGGED_ATTESTATION: ConstOid + = Oid(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 1, 36]); pub const PE_AUTHORITY_INFO_ACCESS: Oid<&[u8]> = Oid(&[43, 6, 1, 5, 5, 7, 1, 1]); diff --git a/src/resources/asres.rs b/src/resources/asres.rs index 8dc513f0..7cf16077 100644 --- a/src/resources/asres.rs +++ b/src/resources/asres.rs @@ -325,11 +325,23 @@ impl AsBlocks { /// # Decoding and Encoding /// impl AsBlocks { + pub fn take_from( + cons: &mut decode::Constructed + ) -> Result { + cons.take_sequence(Self::parse_cons_content) + } + /// Parses the content of a AS ID blocks sequence. fn parse_content( content: &mut decode::Content ) -> Result { let cons = content.as_constructed()?; + Self::parse_cons_content(cons) + } + + fn parse_cons_content( + cons: &mut decode::Constructed + ) -> Result { let mut err = None; let res = SharedChain::from_iter( @@ -529,7 +541,7 @@ impl AsBlock { impl AsBlock { /// Takes an optional AS bock from the beginning of an encoded value. - fn take_opt_from( + pub fn take_opt_from( cons: &mut decode::Constructed ) -> Result, S::Err> { cons.take_opt_value(|tag, content| { @@ -545,9 +557,8 @@ impl AsBlock { }) } - /* /// Skips over the AS block at the beginning of an encoded value. - fn skip_opt_in( + pub fn skip_opt_in( cons: &mut decode::Constructed ) -> Result, S::Err> { cons.take_opt_value(|tag, content| { @@ -562,7 +573,6 @@ impl AsBlock { } }) } - */ fn encode(self) -> impl encode::Values { match self { @@ -710,7 +720,6 @@ impl AsRange { }) } - /* /// Skips over the content of an AS range value. fn skip_content( content: &mut decode::Content @@ -720,7 +729,6 @@ impl AsRange { AsId::skip_in(cons)?; Ok(()) } - */ fn encode(self) -> impl encode::Values { encode::sequence(( @@ -779,14 +787,12 @@ impl AsId { cons.take_u32().map(AsId) } - /* /// Skips over the AS number at the beginning of an encoded value. fn skip_in( cons: &mut decode::Constructed ) -> Result<(), S::Err> { cons.take_u32().map(|_| ()) } - */ /// Parses the content of an AS number value. fn parse_content( @@ -795,14 +801,12 @@ impl AsId { content.to_u32().map(AsId) } - /* /// Skips the content of an AS number value. fn skip_content( content: &mut decode::Content ) -> Result<(), S::Err> { content.to_u32().map(|_| ()) } - */ pub fn encode(self) -> impl encode::Values { self.0.encode() diff --git a/src/resources/ipres.rs b/src/resources/ipres.rs index 3d19b522..9ad608aa 100644 --- a/src/resources/ipres.rs +++ b/src/resources/ipres.rs @@ -348,11 +348,23 @@ impl IpBlocks { } impl IpBlocks { - /// Parses the content of an address block sequence. + pub fn take_from( + cons: &mut decode::Constructed + ) -> Result { + cons.take_sequence(Self::parse_cons_content) + } + + /// Parses the content of a AS ID blocks sequence. fn parse_content( content: &mut decode::Content ) -> Result { let cons = content.as_constructed()?; + Self::parse_cons_content(cons) + } + + fn parse_cons_content( + cons: &mut decode::Constructed + ) -> Result { let mut err = None; let res = SharedChain::from_iter( @@ -548,7 +560,7 @@ impl IpBlock { impl IpBlock { /// Takes an optional address block from the beginning of encoded value. - fn take_opt_from( + pub fn take_opt_from( cons: &mut decode::Constructed ) -> Result, S::Err> { cons.take_opt_value(|tag, content| { diff --git a/src/roa.rs b/src/roa.rs index d24e927e..6455f789 100644 --- a/src/roa.rs +++ b/src/roa.rs @@ -63,12 +63,12 @@ impl Roa { self.signed.encode_ref() } - /// Returns a DER encoded Captured for this. + /// Returns a DER encoded Captured for this ROA. pub fn to_captured(&self) -> Captured { self.encode_ref().to_captured(Mode::Der) } - /// Returns a reference to the EE certificate of this manifest. + /// Returns a reference to the EE certificate of this ROA. pub fn cert(&self) -> &Cert { self.signed.cert() } diff --git a/src/rta.rs b/src/rta.rs new file mode 100644 index 00000000..df633505 --- /dev/null +++ b/src/rta.rs @@ -0,0 +1,852 @@ +//! Resource Tagged Attestations. +//! +//! Resouce Tagged Attestations attaching signed attestations of ownership of +//! resources referred to by a docuement. This is currently an IETF draft, +//! see [draft-michaelson-rpki-rta] for details. +//! +//! draft-michaelson-rpki-rta: https://tools.ietf.org/html/draft-michaelson-rpki-rta + +use bcder::{decode, encode}; +use bcder::{Captured, Mode, OctetString, Tag}; +use bcder::encode::{PrimitiveContent, Values}; +use bcder::string::OctetStringSource; +use bcder::xerr; +use bytes::Bytes; +use crate::oid; +use crate::cert::{Cert, ResourceCert}; +use crate::crl::Crl; +use crate::crypto:: { + DigestAlgorithm, KeyIdentifier, Signature, SignatureAlgorithm, + Signer, SigningError +}; +use crate::resources::{ + AddressFamily, AsBlock, AsBlocksBuilder, IpBlock, IpBlocksBuilder +}; +use crate::sigobj::{MessageDigest, SignedAttrs}; +use crate::x509::{Time, ValidationError}; + + +//------------ Rta ----------------------------------------------------------- + +#[derive(Clone, Debug)] +pub struct Rta { + signed: MultiSignedObject, + content: ResourceTaggedAttestation, +} + +impl Rta { + pub fn decode( + source: S, + strict: bool + ) -> Result { + let signed = MultiSignedObject::decode(source, strict)?; + let content = signed.decode_content(|cons| { + ResourceTaggedAttestation::take_from(cons) + })?; + Ok(Rta { signed, content }) + } + + pub fn validate Result>( + self, _strict: bool, _validate_cert: F + ) -> Result { + unimplemented!() + } + + /// Returns a value encoder for a reference to a ROA. + pub fn encode_ref<'a>(&'a self) -> impl encode::Values + 'a { + self.signed.encode_ref() + } + + /// Returns a DER encoded Captured for this ROA. + pub fn to_captured(&self) -> Captured { + self.encode_ref().to_captured(Mode::Der) + } +} + + +//------------ ResourceTaggedAttestation ------------------------------------- + +#[derive(Clone, Debug)] +pub struct ResourceTaggedAttestation { + subject_keys: SubjectKeySet, + + /// AS Resources + as_resources: RtaAsBlocks, + + /// IP Resources for the IPv4 address family. + v4_resources: RtaIpBlocks, + + /// IP Resources for the IPv6 address family. + v6_resources: RtaIpBlocks, + + digest_algorithm: DigestAlgorithm, + + message_digest: MessageDigest, +} + +impl ResourceTaggedAttestation { + fn take_from( + cons: &mut decode::Constructed + ) -> Result { + cons.take_sequence(|cons| { + cons.take_opt_constructed_if(Tag::CTX_0, |c| c.skip_u8_if(0))?; + let subject_keys = SubjectKeySet::take_from(cons)?; + let (v4res, v6res, asres) = Self::take_resources_from(cons)?; + let alg = DigestAlgorithm::take_from(cons)?; + let digest = OctetString::take_from(cons)?; + Ok(ResourceTaggedAttestation { + subject_keys, + v4_resources: v4res, + v6_resources: v6res, + as_resources: asres, + digest_algorithm: alg, + message_digest: digest.into(), + }) + }) + } + + fn take_resources_from( + cons: &mut decode::Constructed + ) -> Result<(RtaIpBlocks, RtaIpBlocks, RtaAsBlocks), S::Err> { + cons.take_sequence(|cons| { + let asres = cons.take_opt_constructed_if(Tag::CTX_0, |cons| { + RtaAsBlocks::take_from(cons) + })?; + + let mut v4 = None; + let mut v6 = None; + cons.take_opt_constructed_if(Tag::CTX_1, |cons| { + cons.take_sequence(|cons| { + while let Some(()) = cons.take_opt_sequence(|cons| { + match AddressFamily::take_from(cons)? { + AddressFamily::Ipv4 => { + if v4.is_some() { + xerr!(return Err(decode::Malformed.into())); + } + v4 = Some(RtaIpBlocks::take_from(cons)?); + } + AddressFamily::Ipv6 => { + if v6.is_some() { + xerr!(return Err(decode::Malformed.into())); + } + v6 = Some(RtaIpBlocks::take_from(cons)?); + } + } + Ok(()) + })? { } + Ok(()) + }) + })?; + + if asres.is_none() && v4.is_none() && v6.is_none() { + xerr!(return Err(decode::Malformed.into())); + } + Ok(( + v4.unwrap_or_default(), + v6.unwrap_or_default(), + asres.unwrap_or_default(), + )) + }) + } + + pub fn encode_ref<'a>(&'a self) -> impl encode::Values + 'a { + encode::sequence(( + // version is DEFAULT + self.subject_keys.encode_ref(), + encode::sequence(( + self.encode_as_resources(), + self.encode_ip_resources(), + )), + self.digest_algorithm.encode(), + self.message_digest.encode_ref() + )) + } + + fn encode_as_resources<'a>(&'a self) -> impl encode::Values + 'a { + if self.as_resources.is_empty() { + return None + } + Some(encode::sequence_as(Tag::CTX_0, + self.as_resources.encode_ref() + )) + } + + fn encode_ip_resources<'a>(&'a self) -> impl encode::Values + 'a { + if self.v4_resources.is_empty() && self.v6_resources.is_empty() { + return None + } + Some(encode::sequence_as(Tag::CTX_1, + encode::sequence(( + self.v4_resources.encode_ref_family([0x00, 0x01]), + self.v6_resources.encode_ref_family([0x00, 0x02]), + )) + )) + } + + fn to_bytes(&self) -> Bytes { + self.encode_ref().to_captured(Mode::Der).into_bytes() + } +} + + +//------------ SubjectKeySet ------------------------------------------------- + +// The captured content only contains the content of the set. +#[derive(Clone, Debug)] +pub struct SubjectKeySet(Captured); + +impl SubjectKeySet { + fn take_from( + cons: &mut decode::Constructed + ) -> Result { + cons.take_set(|cons| { + cons.capture(|cons| { + KeyIdentifier::skip_in(cons) + }).map(SubjectKeySet) + }) + } + + fn encode_ref<'a>(&'a self) -> impl encode::Values + 'a { + encode::set( + &self.0 + ) + } +} + +impl From> for SubjectKeySet { + fn from(src: Vec) -> Self { + SubjectKeySet(Captured::from_values( + Mode::Der, + encode::iter(src.iter().map(|item| item.encode_ref())) + )) + } +} + + +//------------ RtaAsBlocks --------------------------------------------------- + +#[derive(Clone, Debug)] +pub struct RtaAsBlocks(Captured); + +impl RtaAsBlocks { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl RtaAsBlocks { + fn take_from( + cons: &mut decode::Constructed + ) -> Result { + cons.take_sequence(|cons| { + cons.capture(|cons| { + while AsBlock::skip_opt_in(cons)?.is_some() { } + Ok(()) + }) + }).map(RtaAsBlocks) + } + + fn encode_ref<'a>(&'a self) -> impl encode::Values + 'a { + encode::sequence( + &self.0 + ) + } +} + +impl Default for RtaAsBlocks { + fn default() -> Self { + RtaAsBlocks(Captured::empty(Mode::Der)) + } +} + +impl From for RtaAsBlocks { + fn from(src: AsBlocksBuilder) -> RtaAsBlocks { + RtaAsBlocks(Captured::from_values( + Mode::Der, + src.finalize().encode() + )) + } +} + + +//------------ RtaIpBlocks --------------------------------------------------- + +#[derive(Clone, Debug)] +pub struct RtaIpBlocks(Captured); + +impl RtaIpBlocks { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl RtaIpBlocks { + fn take_from( + cons: &mut decode::Constructed + ) -> Result { + cons.take_sequence(|cons| { + cons.capture(|cons| { + while IpBlock::take_opt_from(cons)?.is_some() { } + Ok(()) + }) + }).map(RtaIpBlocks) + } + + fn encode_ref_family<'a>( + &'a self, + family: [u8; 2] + ) -> impl encode::Values + 'a { + if self.0.is_empty() { + None + } + else { + Some(encode::sequence(( + OctetString::encode_slice(family), + &self.0 + ))) + } + } +} + +impl Default for RtaIpBlocks { + fn default() -> Self { + RtaIpBlocks(Captured::empty(Mode::Der)) + } +} + +impl From for RtaIpBlocks { + fn from(src: IpBlocksBuilder) -> RtaIpBlocks { + RtaIpBlocks(Captured::from_values( + Mode::Der, + src.finalize().encode() + )) + } +} + + +//------------ MultiSignedObject --------------------------------------------- + +/// The flavour of a signed object used for RTAs. +#[derive(Clone, Debug)] +pub struct MultiSignedObject { + digest_algorithm: DigestAlgorithm, + content: OctetString, + certificates: CertificateSet, + crls: CrlSet, + signer_infos: SignerInfoSet, +} + +impl MultiSignedObject { + /// Returns a reference to the object’s content. + pub fn content(&self) -> &OctetString { + &self.content + } + + /// Decodes the object’s content. + pub fn decode_content(&self, op: F) -> Result + where F: FnOnce(&mut decode::Constructed) + -> Result { + Mode::Der.decode(self.content.to_source(), op) + } +} + +impl MultiSignedObject { + pub fn decode( + source: S, + _strict: bool + ) -> Result { + Mode::Der.decode(source, Self::take_from) + } + + pub fn take_from( + cons: &mut decode::Constructed + ) -> Result { + cons.take_sequence(|cons| { + oid::SIGNED_DATA.skip_if(cons)?; // contentType + cons.take_constructed_if(Tag::CTX_0, |cons| { // content + cons.take_sequence(|cons| { // SignedData + cons.skip_u8_if(3)?; // version -- must be 3 + let digest_algorithm = + DigestAlgorithm::take_set_from(cons)?; + let content = cons.take_sequence(|cons| { + // encapContentInfo + oid::CT_RESOURCE_TAGGED_ATTESTATION.skip_if(cons)?; + cons.take_constructed_if( + Tag::CTX_0, + OctetString::take_from + ) + })?; + let certificates = CertificateSet::take_if( + Tag::CTX_0, cons + )?; + let crls = CrlSet::take_opt_if(Tag::CTX_1, cons)?; + let signer_infos = SignerInfoSet::take_from(cons)?; + + Ok(MultiSignedObject { + digest_algorithm, + content, + certificates, + crls, + signer_infos, + }) + }) + }) + }) + } + + pub fn validate Result>( + self, _strict: bool, _validate_cert: F + ) -> Result { + unimplemented!() + } + + /// Returns a value encoder for a reference to a signed object. + pub fn encode_ref<'a>(&'a self) -> impl encode::Values + 'a { + encode::sequence(( + oid::SIGNED_DATA.encode(), // contentType + encode::sequence_as(Tag::CTX_0, // content + encode::sequence(( + 3u8.encode(), // version + self.digest_algorithm.encode_set(), // digestAlgorithms + encode::sequence(( // encapContentInfo + oid::CT_RESOURCE_TAGGED_ATTESTATION.encode(), + encode::sequence_as(Tag::CTX_0, + self.content.encode_ref() + ), + )), + encode::sequence_as(Tag::CTX_0, // certificates + self.certificates.encode_ref(), + ), + self.crls.encode_ref_as(Tag::CTX_1), + encode::sequence(self.signer_infos.encode_ref()), + )), + ) + )) + } +} + + +//------------ CertificateSet ------------------------------------------------ + +// The captured content is the content of the set. We only support a +// certificate choice of a certificate. +#[derive(Clone, Debug)] +pub struct CertificateSet(Captured); + +impl From> for CertificateSet { + fn from(src: Vec) -> CertificateSet { + CertificateSet(Captured::from_values( + Mode::Der, + encode::iter(src.iter().map(|item| item.encode_ref())) + )) + } +} + +impl CertificateSet { + pub fn collect_certs(self) -> Vec { + self.0.decode(|cons| { + let mut res = Vec::new(); + while let Some(cert) = Cert::take_opt_from(cons)? { + res.push(cert) + } + Ok(res) + }).unwrap() + } +} + +impl CertificateSet { + pub fn take_if( + tag: Tag, cons: &mut decode::Constructed + ) -> Result { + cons.take_constructed_if(tag, |cons| { + cons.capture(|cons| { + while Cert::take_opt_from(cons)?.is_some() { } + Ok(()) + }) + }).map(CertificateSet) + } + + /// Returns a value encoder for a reference to a signed object. + pub fn encode_ref<'a>(&'a self) -> impl encode::Values + 'a { + &self.0 + } +} + + +//------------ CrlSet -------------------------------------------------------- + +// The captured content is the content of the set. We only support a +// choice of a CRL. +#[derive(Clone, Debug)] +pub struct CrlSet(Captured); + +impl From> for CrlSet { + fn from(src: Vec) -> CrlSet { + CrlSet(Captured::from_values( + Mode::Der, + encode::iter(src.iter().map(|item| item.encode_ref())) + )) + } +} + +impl CrlSet { + pub fn collect_crls(self) -> Vec { + self.0.decode(|cons| { + let mut res = Vec::new(); + while let Some(crl) = Crl::take_opt_from(cons)? { + res.push(crl) + } + Ok(res) + }).unwrap() + } +} + +impl CrlSet { + pub fn take_opt_if( + tag: Tag, cons: &mut decode::Constructed + ) -> Result { + let res = cons.take_opt_constructed_if(tag, |cons| { + cons.capture(|cons| { + while Crl::take_opt_from(cons)?.is_some() { } + Ok(()) + }) + })?; + Ok(CrlSet(res.unwrap_or_else(|| Captured::empty(Mode::Der)))) + } + + + /// Returns a value encoder for a reference to a signed object. + pub fn encode_ref_as<'a>(&'a self, tag: Tag) -> impl encode::Values + 'a { + match self.0.is_empty() { + true => None, + false => { + Some(encode::sequence_as(tag, &self.0)) + } + } + } +} + + +//------------ SignerInfoSet ------------------------------------------------- + +#[derive(Clone, Debug)] +pub struct SignerInfoSet(Captured); + +impl From> for SignerInfoSet { + fn from(src: Vec) -> SignerInfoSet { + SignerInfoSet(Captured::from_values( + Mode::Der, + encode::iter(src.iter().map(|item| item.encode_ref())) + )) + } +} + +impl SignerInfoSet { + pub fn collect_signer_infos( + self, + ) -> Vec { + self.0.decode(|cons| { + let mut res = Vec::new(); + while let Some(crl) = SignerInfo::take_opt_from( + cons + )? { + res.push(crl) + } + Ok(res) + }).unwrap() + } +} + +impl SignerInfoSet { + pub fn take_from( + cons: &mut decode::Constructed + ) -> Result { + cons.take_set(|cons| { + cons.capture(|cons| { + while SignerInfo::take_opt_from(cons)?.is_some() { } + Ok(()) + }) + }).map(SignerInfoSet) + } + + /// Returns a value encoder for a reference to a signed object. + pub fn encode_ref<'a>( &'a self) -> impl encode::Values + 'a { + &self.0 + } +} + + +//------------ SignerInfo ---------------------------------------------------- + +/// A single SignerInfo of a signed object. +#[derive(Clone, Debug)] +pub struct SignerInfo { + sid: KeyIdentifier, + digest_algorithm: DigestAlgorithm, + signed_attrs: SignedAttrs, + signature: Signature, + + //--- SignedAttributes + // + message_digest: MessageDigest, + signing_time: Option