418 lines
12 KiB
Rust
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),
|
||
|
}
|