wrap some rr fields in flags/enum types with some derived help, add [c]dnskey tag calculation

This commit is contained in:
Stefan Bühler 2019-07-04 23:54:43 +02:00
parent 155b34a17a
commit f5a6ce44c7
9 changed files with 513 additions and 26 deletions

View File

@ -65,6 +65,14 @@ impl DnsTextData for HexShortBlob {
}
}
impl std::ops::Deref for HexShortBlob {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
// 16-bit length, uses decimal length + base64 encoding (if length > 0)
// for text representation.
//
@ -116,6 +124,14 @@ impl DnsTextData for Base64LongBlob {
}
}
impl std::ops::Deref for Base64LongBlob {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// No length byte (or restriction), just all data to end of record. uses
/// base64 encoding for text representation, whitespace allowed, padding
/// required.
@ -154,6 +170,14 @@ impl DnsTextData for Base64RemainingBlob {
}
}
impl std::ops::Deref for Base64RemainingBlob {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// No length byte (or restriction), just all data to end of record. uses
/// hex encoding for text representation, whitespace allowed.
///
@ -187,6 +211,14 @@ impl DnsTextData for HexRemainingBlob {
}
}
impl std::ops::Deref for HexRemainingBlob {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// No length byte (or restriction), just all data to end of record. uses
/// hex encoding for text representation, whitespace allowed.
///
@ -226,3 +258,11 @@ impl DnsTextData for HexRemainingBlobNotEmpty {
write!(f, "{}", HEXLOWER_PERMISSIVE_ALLOW_WS.encode(&self.0))
}
}
impl std::ops::Deref for HexRemainingBlobNotEmpty {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}

View File

@ -0,0 +1,9 @@
use crate::ser::{packet::DnsPacketData, text::DnsTextData};
#[dnsbox_derive::native_flags(u8)]
#[derive(DnsPacketData, DnsTextData)]
/// Flags for the CAA RR
pub enum CaaFlags {
/// Issuer Critical Flag
CRITICAL = 0x01, // bit "7"
}

View File

@ -0,0 +1,125 @@
use crate::ser::{packet::DnsPacketData, text::DnsTextData};
// https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
#[dnsbox_derive::native_enum(u8)]
#[derive(DnsPacketData, DnsTextData)]
pub enum DnsSecAlgorithm {
/// Delete DS
// [RFC4034][RFC4398][RFC8078]
DELETE = 0,
/// RSA/MD5 (deprecated, see 5)
// [RFC3110][RFC4034]
RSAMD5 = 1,
/// Diffie-Hellman
// [RFC2539][proposed standard]
DH = 2,
/// DSA/SHA1
// [RFC3755][proposed standard][RFC2536][proposed standard][Federal Information Processing Standards Publication (FIPS PUB) 186, Digital Signature Standard, 18 May 1994.][Federal Information Processing Standards Publication (FIPS PUB) 180-1, Secure Hash Standard, 17 April 1995. (Supersedes FIPS PUB 180 dated 11 May 1993.)]
DSA = 3,
// Reserved: 4 [RFC6725]
/// RSA/SHA-1
// [RFC3110][RFC4034]
RSASHA1 = 5,
/// DSA-NSEC3-SHA1
// [RFC5155][proposed standard]
DSA_NSEC3_SHA1 = 6,
/// RSASHA1-NSEC3-SHA1
// [RFC5155][proposed standard]
RSASHA1_NSEC3_SHA1 = 7,
/// RSA/SHA-256
// [RFC5702][proposed standard]
RSASHA256 = 8,
// Reserved: 9 [RFC6725]
/// RSA/SHA-512
// [RFC5702][proposed standard]
RSASHA512 = 10,
// Reserved: 11 [RFC6725]
/// GOST R 34.10-2001
// [RFC5933][standards track]
ECC_GOST = 12,
/// ECDSA Curve P-256 with SHA-256
// [RFC6605][standards track]
ECDSAP256SHA256 = 13,
/// ECDSA Curve P-384 with SHA-384
// [RFC6605][standards track]
ECDSAP384SHA384 = 14,
/// Ed25519
// [RFC8080][standards track]
ED25519 = 15,
/// Ed448
// [RFC8080][standards track]
ED448 = 16,
/// Reserved for Indirect Keys
// [RFC4034][proposed standard]
INDIRECT = 252,
/// private algorithm
// [RFC4034]
PRIVATEDNS = 253,
/// private algorithm OID
// [RFC4034]
PRIVATEOID = 254,
// Reserved: 255 [RFC4034][proposed standard]
}
#[dnsbox_derive::native_flags(u16)]
#[derive(DnsPacketData, DnsTextData)]
/// Flags for the DNSKEY RR
pub enum DnskeyFlags {
ZONE_KEY = 0x0100, // bit "7"
/// secure entry point, SEP
SECURE_ENTRY_POINT = 0x0001, // bit "15"
}
#[dnsbox_derive::native_enum(u8)]
#[derive(DnsPacketData, DnsTextData)]
/// Protocol for the DNSKEY RR (only DNSSEC(3) is valid for DNSKEY)
pub enum DnskeyProtocol {
// reserved: 0x00
TLS = 0x01,
EMAIL = 0x02,
DNSSEC = 0x03,
IPSEC = 0x04,
ALL = 0xff,
}
// https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml
#[dnsbox_derive::native_enum(u8)]
#[derive(DnsPacketData, DnsTextData)]
pub enum DnsSecDigestAlgorithm {
// Reserved: 0 [RFC3658]
SHA1 = 0x01, // [RFC3658]
SHA256 = 0x02, // [RFC4509]
GOST_R_34_11_94 = 0x03, // [RFC5933]
SHA384 = 0x04, // [RFC6605]
}
// https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml
#[dnsbox_derive::native_flags(u8)]
#[derive(DnsPacketData, DnsTextData)]
/// Flags for the NSEC3 RR
pub enum Nsec3Flags {
OPT_OUT = 0x01, // bit "7"
}
// https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml
#[dnsbox_derive::native_flags(u8)]
#[derive(DnsPacketData, DnsTextData)]
/// Flags for the NSEC3PARAM RR
pub enum Nsec3ParamFlags {
// reserved: Nsec3Flags::OPT_OUT bit "7" (0x01)
}
// https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml
#[dnsbox_derive::native_enum(u8)]
#[derive(DnsPacketData, DnsTextData)]
pub enum Nsec3Algorithm {
// Reserved: 0 [RFC5155]
SHA1 = 0x01, // [RFC5155]
}

View File

@ -3,20 +3,26 @@ pub mod classes;
pub mod name;
pub mod text;
pub mod types;
mod caa;
mod dnssec;
mod eui;
mod nsec;
mod nxt;
mod sig;
mod sshfp;
mod time;
mod uri;
pub use self::binary::{HexShortBlob, Base64LongBlob, Base64RemainingBlob, HexRemainingBlob, HexRemainingBlobNotEmpty};
pub use self::classes::Class;
pub use self::caa::{CaaFlags};
pub use self::dnssec::{DnsSecAlgorithm, DnskeyFlags, DnskeyProtocol, DnsSecDigestAlgorithm, Nsec3Flags, Nsec3ParamFlags, Nsec3Algorithm};
pub use self::eui::{EUI48Addr, EUI64Addr};
pub use self::name::{DnsName, DnsCanonicalName, DnsCompressedName};
pub use self::nsec::{NsecTypeBitmap, NextHashedOwnerName};
pub use self::nxt::NxtTypeBitmap;
pub use self::sig::OptionalTTL;
pub use self::sshfp::{SshFpAlgorithm, SshFpType};
pub use self::text::{ShortText, LongText, UnquotedShortText, RemainingText};
pub use self::time::{Time, TimeStrict, Time48};
pub use self::types::Type;

View File

@ -0,0 +1,25 @@
use crate::ser::{packet::DnsPacketData, text::DnsTextData};
// https://www.iana.org/assignments/dns-sshfp-rr-parameters/dns-sshfp-rr-parameters.xml
#[dnsbox_derive::native_enum(u8)]
#[derive(DnsPacketData, DnsTextData)]
pub enum SshFpAlgorithm {
// Reserved: 0
// [RFC4255]
RSA = 1,
// [RFC4255]
DSA = 2,
// [RFC6594]
ECDSA = 3,
// [RFC7479]
ED25519 = 4,
}
#[dnsbox_derive::native_enum(u8)]
#[derive(DnsPacketData, DnsTextData)]
pub enum SshFpType {
SHA1 = 1,
SHA256 = 2,
}

View File

@ -179,7 +179,7 @@ pub struct NSAP_PTR {
#[RRClass(ANY)]
pub struct SIG {
pub rr_type: Type,
pub algorithm: u8,
pub algorithm: DnsSecAlgorithm,
pub labels: u8,
// RFC says this can be omitted in text form if it is the same as
// the TTL on the SIG record. not supported to be omitted here
@ -196,8 +196,8 @@ pub struct SIG {
#[RRClass(ANY)]
pub struct KEY {
pub flags: u16,
pub protocol: u8,
pub algorithm: u8,
pub protocol: DnskeyProtocol,
pub algorithm: DnsSecAlgorithm,
pub public_key: Base64RemainingBlob,
}
@ -274,9 +274,10 @@ pub struct KX {
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// #[RRClass(?)]
pub struct CERT {
// https://www.iana.org/assignments/cert-rr-types/cert-rr-types.xhtml
pub cert_type: u16,
pub key_tag: u16,
pub algorithm: u8,
pub algorithm: DnsSecAlgorithm,
pub certificate: Base64RemainingBlob,
}
@ -304,16 +305,16 @@ pub struct DNAME {
#[RRClass(ANY)]
pub struct DS {
pub key_tag: u16,
pub algorithm: u8,
pub digest_type: u8,
pub algorithm: DnsSecAlgorithm,
pub digest_type: DnsSecDigestAlgorithm,
pub digest: HexRemainingBlob,
}
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// #[RRClass(?)]
pub struct SSHFP {
pub algorithm: u8,
pub fingerprint_type: u8,
pub algorithm: SshFpAlgorithm,
pub fingerprint_type: SshFpType,
// RFC 4255 doesn't specify whether whitespace is allowed.
// `HexRemainingBlob` allows whitespace.
pub fingerprint: HexRemainingBlob,
@ -325,7 +326,7 @@ pub use super::weird_structs::IPSECKEY;
#[RRClass(ANY)]
pub struct RRSIG {
pub rr_type: Type,
pub algorithm: u8,
pub algorithm: DnsSecAlgorithm,
pub labels: u8,
pub original_ttl: u32,
pub signature_expiration: Time,
@ -345,12 +346,55 @@ pub struct NSEC {
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(ANY)]
pub struct DNSKEY {
pub flags: u16,
pub protocol: u8,
pub algorithm: u8,
pub flags: DnskeyFlags,
pub protocol: DnskeyProtocol, // should always be DNSSEC (3) according to RFC 4034
pub algorithm: DnsSecAlgorithm,
pub public_key: Base64RemainingBlob,
}
impl DNSKEY {
fn alg1_tag(&self) -> u16 {
let key: &[u8] = &self.public_key;
if key.is_empty() { return 0; } // not enough data
let pkey;
if 0 == key[0] {
// two-byte length encoding of exponent
if key.len() < 3 { return 0; } // not enough data
let explen = ((key[1] as u16) << 8) + (key[2] as u16);
if explen < 256 { return 0; } // should have used shorter length encoding
if key.len() < 3 + (explen as usize) { return 0; } // not enough data
pkey = &key[3 + (explen as usize)..];
} else {
// one-byte length encoding of exponent
let explen = key[0];
if key.len() < 1 + (explen as usize) { return 0; } // not enough data
pkey = &key[1 + (explen as usize)..];
}
if pkey.len() < 3 { return 0; } // not enough data
((pkey[pkey.len() - 3] as u16) << 8) + (pkey[pkey.len() - 3] as u16)
}
/// calculate key tag
pub fn tag(&self) -> u16 {
if self.algorithm == DnsSecAlgorithm::RSAMD5 { return self.alg1_tag(); }
let mut sum = 0u32;
sum += self.flags.0 as u32;
sum += (self.protocol.0 as u32) << 8;
sum += self.algorithm.0 as u32;
let key: &[u8] = &self.public_key;
for i in 0..key.len() {
let v = key[i] as u32;
let v = if 0 == i & 1 { v << 8 } else { v };
sum = sum.wrapping_add(v);
}
sum.wrapping_add(sum >> 16) as u16
}
}
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(IN)]
pub struct DHCID {
@ -360,8 +404,8 @@ pub struct DHCID {
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(ANY)]
pub struct NSEC3 {
pub hash_algorithm: u8,
pub flags: u8,
pub hash_algorithm: Nsec3Algorithm,
pub flags: Nsec3Flags,
pub iterations: u16,
pub salt: HexShortBlob,
pub next_hashed: NextHashedOwnerName,
@ -371,8 +415,8 @@ pub struct NSEC3 {
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(ANY)]
pub struct NSEC3PARAM {
pub hash_algorithm: u8,
pub flags: u8,
pub hash_algorithm: Nsec3Algorithm,
pub flags: Nsec3ParamFlags,
pub iterations: u16,
pub salt: HexShortBlob,
}
@ -413,7 +457,7 @@ pub struct NINFO {
pub struct RKEY {
pub flags: u16,
pub protocol: u8,
pub algorithm: u8,
pub algorithm: DnsSecAlgorithm,
pub public_key: Base64RemainingBlob,
}
@ -425,20 +469,31 @@ pub struct RKEY {
#[RRClass(ANY)]
pub struct CDS {
pub key_tag: u16,
pub algorithm: u8,
pub digest_type: u8,
pub algorithm: DnsSecAlgorithm,
pub digest_type: DnsSecDigestAlgorithm,
pub digest: HexRemainingBlob,
}
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(ANY)]
pub struct CDNSKEY {
pub flags: u16,
pub protocol: u8,
pub algorithm: u8,
pub flags: DnskeyFlags,
pub protocol: DnskeyProtocol,
pub algorithm: DnsSecAlgorithm,
pub public_key: Base64RemainingBlob,
}
impl CDNSKEY {
pub fn tag(&self) -> u16 {
DNSKEY {
flags: self.flags,
protocol: self.protocol,
algorithm: self.algorithm,
public_key: self.public_key.clone(),
}.tag()
}
}
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(ANY)]
pub struct OPENPGPKEY {
@ -545,7 +600,7 @@ pub struct URI {
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// #[RRClass(?)]
pub struct CAA {
pub flags: u8,
pub flags: CaaFlags,
pub tag: UnquotedShortText,
pub value: RemainingText,
}
@ -559,8 +614,8 @@ pub struct CAA {
#[RRClass(ANY)]
pub struct DLV {
pub key_tag: u16,
pub algorithm: u8,
pub digest_type: u8,
pub algorithm: DnsSecAlgorithm,
pub digest_type: DnsSecDigestAlgorithm,
pub digest: HexRemainingBlob,
}

View File

@ -9,11 +9,14 @@ extern crate proc_macro2;
mod rrdata;
mod dns_packet_data;
mod dns_text_data;
mod native_enum;
mod native_flags;
decl_derive!([DnsPacketData] => dns_packet_data::derive);
decl_derive!([DnsTextData] => dns_text_data::derive);
decl_derive!([RRData, attributes(RRTypeName, RRClass)] => rrdata::rrdata_derive);
decl_attribute!([native_enum] => native_enum::attribute_native_enum);
decl_attribute!([native_flags] => native_flags::attribute_native_flags);
fn attr_get_single_list_arg(attr_meta: &syn::Meta) -> proc_macro2::TokenStream {
match attr_meta {

View File

@ -0,0 +1,88 @@
use proc_macro2::TokenStream;
pub fn attribute_native_enum(
native_type: TokenStream,
structure: synstructure::Structure,
) -> TokenStream {
let ast = structure.ast();
let in_attrs = ast.attrs.iter().map(|a| quote!{#a}).collect::<TokenStream>();
let in_vis = &ast.vis;
let name = &ast.ident;
let known_name = syn::Ident::new(&format!("{}Known", name), proc_macro2::Span::call_site());
let enumdata = match &ast.data {
syn::Data::Enum(de) => de,
_ => panic!("not an enum"),
};
let known_enum;
{
let variants = &enumdata.variants;
let doc_str = format!("Known enum variants of [`{}`]\n\n[`{}`]: struct.{}.html\n", name, name, name);
known_enum = quote! {
#[doc = #doc_str]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(#native_type)]
#[allow(non_camel_case_types)]
#in_vis enum #known_name {
#variants
}
}
}
let mut consts = TokenStream::new();
let mut convert = TokenStream::new();
for variant in &enumdata.variants {
let variant_attrs = variant.attrs.iter().map(|a| quote!{#a}).collect::<TokenStream>();
let disc_name = &variant.ident;
let disc = variant.discriminant.as_ref().map(|(_, d)| quote!{#d}).unwrap_or_else(|| quote!{
#known_name::#disc_name as #native_type
});
consts.extend(quote! {
#variant_attrs
pub const #disc_name: Self = #name(#disc);
});
convert.extend(quote! {
Self::#disc_name => #known_name::#disc_name,
});
}
quote! {
#known_enum
#in_attrs
#[derive(Clone, Copy, PartialEq, Eq)]
#in_vis struct #name(pub #native_type);
impl #name {
#consts
/// Try converting to known enum values
pub fn into_known(self) -> Option<#known_name> {
Some(
#[allow(unreachable_patterns)]
match self {
#convert
_ => return None,
}
)
}
}
impl core::fmt::Debug for #name {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self.into_known() {
Some(v) => v.fmt(f),
None => f.debug_tuple(stringify!(#name)).field(&self.0).finish(),
}
}
}
impl From<#known_name> for #name {
fn from(v: #known_name) -> Self {
#name(v as #native_type)
}
}
}
}

View File

@ -0,0 +1,136 @@
use proc_macro2::TokenStream;
pub fn attribute_native_flags(
native_type: TokenStream,
structure: synstructure::Structure,
) -> TokenStream {
let ast = structure.ast();
let in_attrs = ast.attrs.iter().map(|a| quote!{#a}).collect::<TokenStream>();
let in_vis = &ast.vis;
let name = &ast.ident;
let hidden_impl_mod = syn::Ident::new(&format!("_{}_hidden_native_flags", name), proc_macro2::Span::call_site());
let enumdata = match &ast.data {
syn::Data::Enum(de) => de,
_ => panic!("not an enum"),
};
let mut consts = TokenStream::new();
let mut known_mask = TokenStream::new();
let mut dbg = TokenStream::new();
for variant in &enumdata.variants {
let variant_attrs = variant.attrs.iter().map(|a| quote!{#a}).collect::<TokenStream>();
let disc_name = &variant.ident;
let disc = variant.discriminant.as_ref().map(|(_, d)| d).expect("all variants need explicit bitmask discriminants");
consts.extend(quote! {
#variant_attrs
pub const #disc_name: Flag = Flag { mask: #disc };
});
known_mask.extend(quote!{
| #disc
});
dbg.extend(quote! {
if self & Self::#disc_name {
if !first { f.write_str(" | ")?; }
first = false;
f.write_str(stringify!(#disc_name))?;
}
});
}
let bitops = quote! {
impl core::ops::BitAnd<Flag> for super::#name {
type Output = bool;
fn bitand(self, rhs: Flag) -> Self::Output {
rhs.mask == self.0 & rhs.mask
}
}
impl core::ops::BitAnd<Flag> for &super::#name {
type Output = bool;
fn bitand(self, rhs: Flag) -> Self::Output {
rhs.mask == self.0 & rhs.mask
}
}
impl core::ops::BitOr for Flag {
type Output = super::#name;
fn bitor(self, rhs: Flag) -> Self::Output {
super::#name(self.mask | rhs.mask)
}
}
impl core::ops::BitOr<Flag> for super::#name {
type Output = super::#name;
fn bitor(self, rhs: Flag) -> Self::Output {
super::#name(self.0 | rhs.mask)
}
}
impl core::ops::BitOr<super::#name> for Flag {
type Output = super::#name;
fn bitor(self, rhs: super::#name) -> Self::Output {
super::#name(self.mask | rhs.0)
}
}
};
quote! {
#in_attrs
///
/// The `Flag` constants can be used to check if a flag is set
/// by using bitwise and: `flags & flag`.
///
/// They can be converted to and combined with bitwise or to set
/// flags. Their bitmask is available as public struct member
/// `mask`.
#[derive(Clone, Copy, PartialEq, Eq)]
#in_vis struct #name(pub #native_type);
mod #hidden_impl_mod {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Flag {
pub mask: #native_type,
}
const known_mask: #native_type = 0 #known_mask;
impl super::#name {
pub fn new_flag(mask: #native_type) -> Flag {
Flag { mask }
}
#consts
}
#bitops
impl From<Flag> for super::#name {
fn from(v: Flag) -> Self {
Self(v.mask)
}
}
impl core::fmt::Debug for super::#name {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut first = true;
let unknown_bits = self.0 & !known_mask;
#dbg
if unknown_bits != 0 {
if !first { f.write_str(" | ")?; }
write!(f, "unknown(0x{:x})", unknown_bits)?;
} else if first {
f.write_str("(empty)")?;
}
Ok(())
}
}
}
}
}