libFenrir/src/dnssec/record.rs

418 lines
12 KiB
Rust

//!
//! Structs and information to create/parse the _fenrir DNSSEC record
//!
//! Encoding and decoding in base85, RFC1924
//!
//!
//! Basic encoding idea:
//! * 1 byte: half-bytes
//! * half: num of addresses
//! * half: num of pubkeys
//! [ # list of addresses
//! * 1 byte: bitfield
//! * 0..1 ipv4/ipv6
//! * 2..4 priority (for failover)
//! * 5..7 weight between priority
//! * 1 byte: public key id
//! * 2 bytes: UDP port
//! * X bytes: IP
//! ]
//! [ # list of pubkeys
//! * 1 byte: pubkey type
//! * 1 byte: pubkey id
//! * Y bytes: pubkey
//! ]
use ::core::num::NonZeroU16;
use ::num_traits::FromPrimitive;
use ::std::{net::IpAddr, vec::Vec};
/*
* Public key data
*/
/// Public Key ID
#[derive(Debug, Copy, Clone)]
pub struct PublicKeyId(u8);
/// Public Key Type
#[derive(::num_derive::FromPrimitive, Debug, Copy, Clone)]
// public enum: use non_exhaustive to force users to add a default case
// so in the future we can expand this easily
#[non_exhaustive]
#[repr(u8)]
pub enum PublicKeyType {
/// ed25519 asymmetric key
Ed25519 = 0,
}
impl PublicKeyType {
/// Get the size of a public key of this kind
pub fn key_len(&self) -> usize {
match &self {
PublicKeyType::Ed25519 => 42,
}
}
}
/// Public Key, with its type and id
#[derive(Debug, Clone)]
pub struct PublicKey {
/// public key raw data
raw: Vec<u8>,
/// type of public key
kind: PublicKeyType,
/// id of public key
id: PublicKeyId,
}
impl PublicKey {
fn raw_len(&self) -> usize {
let size = 2; // Public Key Type + ID
size + self.raw.len()
}
fn encode_into(&self, raw: &mut Vec<u8>) {
raw.push(self.kind as u8);
raw.push(self.id.0);
raw.extend_from_slice(&self.raw);
}
fn decode_raw(raw: &[u8]) -> Result<(Self, usize), Error> {
if raw.len() < 4 {
return Err(Error::NotEnoughData(0));
}
let kind = PublicKeyType::from_u8(raw[0]).unwrap();
let id = PublicKeyId(raw[1]);
if raw.len() < 2 + kind.key_len() {
return Err(Error::NotEnoughData(2));
}
let mut raw_key = Vec::with_capacity(kind.key_len());
raw_key.extend_from_slice(&raw[2..(2 + kind.key_len())]);
let total_length = 2 + kind.key_len();
Ok((
Self {
raw: raw_key,
kind,
id,
},
total_length,
))
}
}
/*
* Address data
*/
/// Priority of each group of addresses
#[derive(::num_derive::FromPrimitive, Debug, Copy, Clone)]
// public enum: use non_exhaustive to force users to add a default case
// so in the future we can expand this easily
#[non_exhaustive]
#[repr(u8)]
pub enum AddressPriority {
/// Initially contact addresses in this priority
P0 = 0,
/// First failover
P1,
/// Second failover
P2,
/// Third failover
P3,
/// Fourth failover
P4,
/// Fifth failover
P5,
/// Sisth failover
P6,
/// Seventh failover
P7,
}
/// Inside of each group, weight of the address
/// This helps in distributing the traffic to multiple authentication servers:
/// * client sums all weights in a group
/// * generate a random number [0..sum_of_weights]
/// * the number indicates which server will take the connection
/// So to equally distribute all connections you just have to use the same
/// weight in the same group
#[derive(::num_derive::FromPrimitive, Debug, Copy, Clone)]
// public enum: use non_exhaustive to force users to add a default case
// so in the future we can expand this easily
#[non_exhaustive]
#[repr(u8)]
pub enum AddressWeight {
/// Minimum weigth: 1
W1 = 0,
/// little weigth: 2
W2,
/// little weigth: 3
W3,
/// medium weigth: 4
W4,
/// medium weigth: 5
W5,
/// heavy weigth: 6
W6,
/// heavy weigth: 7
W7,
/// Maximum weigth: 8
W8,
}
/// Authentication server address information:
/// * ip
/// * udp port
/// * priority
/// * weight within priority
#[derive(Debug, Clone, Copy)]
pub struct Address {
/// Ip address of server, v4 or v6
pub ip: IpAddr,
/// udp port. None means that this address is reachable only
/// with Fenrir over IP
pub port: Option<NonZeroU16>,
/// Priority group of this address
pub priority: AddressPriority,
/// Weight of this address in the priority group
pub weight: AddressWeight,
/// Public key ID used by this address
pub public_key_id: PublicKeyId,
}
impl Address {
fn raw_len(&self) -> usize {
let size = 4; // UDP port + Priority + Weight
match self.ip {
IpAddr::V4(_) => size + 4,
IpAddr::V6(_) => size + 16,
}
}
fn encode_into(&self, raw: &mut Vec<u8>) {
let mut bitfield: u8 = match self.ip {
IpAddr::V4(_) => 0,
IpAddr::V6(_) => 1,
};
bitfield <<= 3;
bitfield |= self.priority as u8;
bitfield <<= 3;
bitfield |= self.weight as u8;
raw.push(bitfield);
raw.push(self.public_key_id.0);
raw.extend_from_slice(
&(match self.port {
Some(port) => port.get().to_le_bytes(),
None => [0, 0], // oh noez, which zero goes first?
}),
);
match self.ip {
IpAddr::V4(ip) => {
let raw_ip = ip.octets();
raw.extend_from_slice(&raw_ip);
}
IpAddr::V6(ip) => {
let raw_ip = ip.octets();
raw.extend_from_slice(&raw_ip);
}
};
}
fn decode_raw(raw: &[u8]) -> Result<(Self, usize), Error> {
if raw.len() < 8 {
return Err(Error::NotEnoughData(0));
}
let ip_type = raw[0] >> 6;
let is_ipv6: bool;
let total_length: usize;
match ip_type {
0 => {
is_ipv6 = false;
total_length = 8;
}
1 => {
total_length = 20;
if raw.len() < total_length {
return Err(Error::NotEnoughData(1));
}
is_ipv6 = true
}
_ => return Err(Error::UnsupportedData(0)),
}
let raw_priority = (raw[0] << 2) >> 5;
let raw_weight = (raw[0] << 5) >> 5;
let priority = AddressPriority::from_u8(raw_priority).unwrap();
let weight = AddressWeight::from_u8(raw_weight).unwrap();
let public_key_id = PublicKeyId(raw[1]);
let raw_port = u16::from_le_bytes(raw[2..3].try_into().unwrap());
let port = if raw_port == 0 {
None
} else {
Some(NonZeroU16::new(raw_port).unwrap())
};
let ip = if is_ipv6 {
let raw_ip: [u8; 16] = raw[4..20].try_into().unwrap();
IpAddr::from(raw_ip)
} else {
let raw_ip: [u8; 4] = raw[4..8].try_into().unwrap();
IpAddr::from(raw_ip)
};
Ok((
Self {
ip,
port,
priority,
weight,
public_key_id,
},
total_length,
))
}
}
/*
* Actual record puuting it all toghether
*/
/// All informations found in the DNSSEC record
#[derive(Debug, Clone)]
pub struct Record {
/// Public keys used by any authentication server
public_keys: Vec<PublicKey>,
/// List of all authentication servers' addresses.
/// Multiple ones can point to the same authentication server
addresses: Vec<Address>,
}
impl Record {
/// Simply encode all the record in base85
pub fn encode(&self) -> Result<String, Error> {
// check possible failure scenarios
if self.public_keys.len() == 0 {
return Err(Error::NoPublicKeyFound);
}
if self.public_keys.len() > 16 {
return Err(Error::Max16PublicKeys);
}
if self.addresses.len() == 0 {
return Err(Error::NoAddressFound);
}
if self.addresses.len() > 16 {
return Err(Error::Max16Addresses);
}
// everything else is all good
let total_size: usize = 1
+ self.addresses.iter().map(|a| a.raw_len()).sum::<usize>()
+ self.public_keys.iter().map(|a| a.raw_len()).sum::<usize>();
let mut raw = Vec::with_capacity(total_size);
// amount of data. addresses, then pubkeys. 4 bits each
let len_combined: u8 = self.addresses.len() as u8;
let len_combined = len_combined << 4;
let len_combined = len_combined | self.public_keys.len() as u8;
raw.push(len_combined);
for address in self.addresses.iter() {
address.encode_into(&mut raw);
}
for public_key in self.public_keys.iter() {
public_key.encode_into(&mut raw);
}
Ok(::base85::encode(&raw))
}
/// Decode from base85 to the actual object
pub fn decode(raw: &[u8]) -> Result<Self, Error> {
// bare minimum for 1 address and key
const MIN_RAW_LENGTH: usize = 1 + 8 + 8;
if raw.len() <= MIN_RAW_LENGTH {
return Err(Error::NotEnoughData(0));
}
let mut num_addresses = (raw[0] >> 4) as usize;
let mut num_public_keys = (raw[0] & 0x0F) as usize;
let mut bytes_parsed = 1;
let mut result = Self {
addresses: Vec::with_capacity(num_addresses),
public_keys: Vec::with_capacity(num_public_keys),
};
while num_addresses > 0 {
let (address, bytes) =
match Address::decode_raw(&raw[bytes_parsed..]) {
Ok(address) => address,
Err(Error::UnsupportedData(b)) => {
return Err(Error::UnsupportedData(bytes_parsed + b))
}
Err(Error::NotEnoughData(b)) => {
return Err(Error::NotEnoughData(bytes_parsed + b))
}
Err(e) => return Err(e),
};
bytes_parsed = bytes_parsed + bytes;
result.addresses.push(address);
num_addresses = num_addresses - 1;
}
while num_public_keys > 0 {
let (public_key, bytes) =
match PublicKey::decode_raw(&raw[bytes_parsed..]) {
Ok(public_key) => public_key,
Err(Error::UnsupportedData(b)) => {
return Err(Error::UnsupportedData(bytes_parsed + b))
}
Err(Error::NotEnoughData(b)) => {
return Err(Error::NotEnoughData(bytes_parsed + b))
}
Err(e) => return Err(e),
};
bytes_parsed = bytes_parsed + bytes;
result.public_keys.push(public_key);
num_public_keys = num_public_keys - 1;
}
if bytes_parsed != raw.len() {
Err(Error::UnknownData(bytes_parsed))
} else {
Ok(result)
}
}
}
/// Possible errors in encoding or decoding the DNSSEC record
#[derive(::thiserror::Error, Debug)]
pub enum Error {
/// General IO error
#[error("IO error: {0:?}")]
IO(#[from] ::std::io::Error),
/// We need at least one address
#[error("no addresses found")]
NoAddressFound,
/// Too many addresses (max 16)
#[error("can't encode more than 16 addresses")]
Max16Addresses,
/// We need at least one public key
#[error("no public keys found")]
NoPublicKeyFound,
/// Maximum 16 public keys supported
#[error("can't encode more than 16 public keys")]
Max16PublicKeys,
/// Not enough data to decode something meaningful
#[error("not enough data. Parsed {0} bytes")]
NotEnoughData(usize),
/// Unsupported Data: can't parse
#[error("Unsupported data. Parsed {0} bytes")]
UnsupportedData(usize),
/// Unknown data at the end
#[error("Unknown data after {0} bytes")]
UnknownData(usize),
}