10
0
Fork 0
mirror of https://github.com/ZeusWPI/ZNS.git synced 2024-10-29 21:14:27 +01:00

add DNSKEY authentication support

This commit is contained in:
Xander Bil 2024-06-09 23:58:40 +02:00
parent 4469b6f0b5
commit c6437e6901
No known key found for this signature in database
GPG key ID: EC9706B54A278598
10 changed files with 138 additions and 83 deletions

View file

@ -1,6 +1,6 @@
use crate::{
errors::DatabaseError,
structs::{Class, Question, Type, RR},
structs::{Class, Type, RR},
};
use diesel::prelude::*;
@ -99,16 +99,17 @@ pub async fn insert_into_database(rr: &RR) -> Result<(), DatabaseError> {
Ok(())
}
pub async fn get_from_database(question: &Question) -> Result<Vec<RR>, DatabaseError> {
pub async fn get_from_database(
name: &Vec<String>,
_type: Type,
class: Class,
) -> Result<Vec<RR>, DatabaseError> {
let db_connection = &mut establish_connection();
let records = Record::get(
db_connection,
question.qname.join("."),
question.qtype.clone().into(),
question.qclass.clone().into(),
)
.map_err(|e| DatabaseError {
let records =
Record::get(db_connection, name.join("."), _type.into(), class.into()).map_err(|e| {
DatabaseError {
message: e.to_string(),
}
})?;
Ok(records

View file

@ -4,7 +4,7 @@ use crate::structs::RCODE;
pub struct DNSError {
pub message: String,
pub rcode: RCODE
pub rcode: RCODE,
}
impl fmt::Display for DNSError {
@ -27,7 +27,7 @@ impl fmt::Display for ParseError {
#[derive(Debug)]
pub struct DatabaseError {
pub message: String
pub message: String,
}
impl fmt::Display for DatabaseError {
@ -77,7 +77,24 @@ where
fn from(value: E) -> Self {
DNSError {
message: value.into().to_string(),
rcode: RCODE::FORMERR
rcode: RCODE::FORMERR,
}
}
}
trait Supported {}
impl Supported for reqwest::Error {}
impl Supported for DatabaseError {}
impl<E> From<E> for AuthenticationError
where
E: Supported,
E: std::fmt::Display,
{
fn from(value: E) -> Self {
AuthenticationError {
message: value.to_string(),
}
}
}

View file

@ -14,7 +14,12 @@ impl ResponseHandler for QueryHandler {
response.header.arcount = 0; //TODO: fix this, handle unknown class values
for question in &message.question {
let answers = get_from_database(&question).await;
let answers = get_from_database(
&question.qname,
question.qtype.clone(),
question.qclass.clone(),
)
.await;
match answers {
Ok(rrs) => {

View file

@ -1,33 +1,30 @@
use reqwest::Error;
use crate::{config::Config, errors::AuthenticationError};
use crate::{
config::Config,
db::models::get_from_database,
errors::{AuthenticationError, DatabaseError},
parser::FromBytes,
reader::Reader,
structs::{Class, RRClass, RRType, Type},
};
use super::sig::{PublicKey, Sig};
use super::{dnskey::DNSKeyRData, sig::Sig};
type SSHKeys = Vec<String>;
type Result<T> = std::result::Result<T, AuthenticationError>;
pub(super) async fn authenticate(sig: &Sig, zone: &Vec<String>) -> Result<bool> {
pub(super) async fn authenticate(
sig: &Sig,
zone: &Vec<String>,
) -> Result<bool, AuthenticationError> {
if zone.len() >= 4 {
let username = &zone[zone.len() - 4]; // Should match: username.users.zeus.gent
let public_keys = get_keys(username).await.map_err(|e| AuthenticationError {
message: e.to_string(),
})?;
Ok(public_keys.iter().any(|key| {
let key_split: Vec<&str> = key.split_ascii_whitespace().collect();
match key_split.len() {
3 => {
let key_encoded = key_split[1].to_string();
match key_split[0] {
"ssh-ed25519" => sig.verify(PublicKey::ED25519(key_encoded)),
_ => false,
let ssh_verified = validate_ssh(username, sig).await?;
if ssh_verified {
Ok(true)
} else {
Ok(validate_dnskey(zone, sig).await?)
}
}
_ => false,
}
}))
} else {
Err(AuthenticationError {
message: String::from("Invalid zone"),
@ -35,13 +32,38 @@ pub(super) async fn authenticate(sig: &Sig, zone: &Vec<String>) -> Result<bool>
}
}
async fn get_keys(username: &String) -> std::result::Result<SSHKeys, Error> {
async fn validate_ssh(username: &String, sig: &Sig) -> Result<bool, Error> {
Ok(reqwest::get(format!(
"{}/users/keys/{}",
Config::get().zauth_url,
username
))
.await?
.json::<SSHKeys>()
.await?)
.json::<Vec<String>>()
.await?
.iter()
.any(|key| {
let key_split: Vec<&str> = key.split_ascii_whitespace().collect();
match key_split.len() {
3 => match key_split[0] {
"ssh-ed25519" => sig.verify_ssh_ed25519(key_split[1]),
_ => false,
},
_ => false,
}
}))
}
async fn validate_dnskey(zone: &Vec<String>, sig: &Sig) -> Result<bool, DatabaseError> {
Ok(
get_from_database(zone, Type::Type(RRType::DNSKEY), Class::Class(RRClass::IN))
.await?
.iter()
.any(|rr| {
let mut reader = Reader::new(&rr.rdata);
DNSKeyRData::from_bytes(&mut reader)
.map(|key| key.verify(sig))
.is_ok_and(|b| b)
}),
)
}

View file

@ -0,0 +1,36 @@
use base64::prelude::*;
use crate::{errors::ParseError, parser::FromBytes, reader::Reader};
use super::sig::Sig;
/// https://datatracker.ietf.org/doc/html/rfc4034#section-2
#[derive(Debug)]
pub(super) struct DNSKeyRData {
pub flags: u16,
pub protocol: u8,
pub algorithm: u8,
pub public_key: Vec<u8>,
}
//TODO: validate values
impl FromBytes for DNSKeyRData {
fn from_bytes(reader: &mut Reader) -> Result<Self, ParseError> {
Ok(DNSKeyRData {
flags: reader.read_u16()?,
protocol: reader.read_u8()?,
algorithm: reader.read_u8()?,
public_key: reader.read(reader.unread_bytes())?,
})
}
}
impl DNSKeyRData {
pub fn verify(&self, sig: &Sig) -> bool {
let encoded = BASE64_STANDARD.encode(&self.public_key);
match self.algorithm {
15 => sig.verify_ed25519(&encoded),
_ => false,
}
}
}

View file

@ -10,6 +10,7 @@ use self::sig::Sig;
use super::ResponseHandler;
mod authenticate;
mod dnskey;
mod sig;
pub(super) struct UpdateHandler {}

View file

@ -12,10 +12,6 @@ pub(super) struct Sig {
key_rdata: KeyRData,
}
pub(super) enum PublicKey {
ED25519(String),
}
impl Sig {
pub fn new(rr: &RR, datagram: &[u8]) -> Result<Sig, ParseError> {
let mut request = datagram[0..datagram.len() - 11 - rr.rdlength as usize].to_vec();
@ -33,7 +29,16 @@ impl Sig {
})
}
fn verify_ed25519(&self, key: String) -> bool {
pub fn verify_ed25519(&self, key: &str) -> bool {
let blob = BASE64_STANDARD.decode(key).unwrap();
let pkey = ring::signature::UnparsedPublicKey::new(&ring::signature::ED25519, &blob);
pkey.verify(&self.raw_data, &self.key_rdata.signature)
.is_ok()
}
pub fn verify_ssh_ed25519(&self, key: &str) -> bool {
let blob = BASE64_STANDARD.decode(key).unwrap();
let pkey = ring::signature::UnparsedPublicKey::new(
@ -44,10 +49,4 @@ impl Sig {
pkey.verify(&self.raw_data, &self.key_rdata.signature)
.is_ok()
}
pub fn verify(&self, key: PublicKey) -> bool {
match key {
PublicKey::ED25519(pkey) => self.verify_ed25519(pkey),
}
}
}

View file

@ -4,8 +4,7 @@ use crate::{
errors::ParseError,
reader::Reader,
structs::{
Class, DNSKeyRData, Header, KeyRData, LabelString, Message, Opcode, Question, RRClass,
RRType, Type, RR,
Class, Header, KeyRData, LabelString, Message, Opcode, Question, RRClass, RRType, Type, RR,
},
};
@ -43,12 +42,14 @@ impl From<Class> for u16 {
impl From<u16> for Type {
fn from(value: u16) -> Self {
//TODO: use macro
match value {
x if x == RRType::A as u16 => Type::Type(RRType::A),
x if x == RRType::OPT as u16 => Type::Type(RRType::OPT),
x if x == RRType::SOA as u16 => Type::Type(RRType::SOA),
x if x == RRType::ANY as u16 => Type::Type(RRType::SOA),
x if x == RRType::KEY as u16 => Type::Type(RRType::KEY),
x if x == RRType::DNSKEY as u16 => Type::Type(RRType::DNSKEY),
x => Type::Other(x),
}
}
@ -327,21 +328,3 @@ impl FromBytes for KeyRData {
}
}
}
impl FromBytes for DNSKeyRData {
fn from_bytes(reader: &mut Reader) -> Result<Self> {
if reader.unread_bytes() < 18 {
Err(ParseError {
object: String::from("DNSKeyRData"),
message: String::from("invalid rdata"),
})
} else {
Ok(DNSKeyRData {
flags: reader.read_u16()?,
protocol: reader.read_u8()?,
algorithm: reader.read_u8()?,
public_key: reader.read(reader.unread_bytes())?,
})
}
}
}

View file

@ -9,7 +9,8 @@ pub enum Type {
pub enum RRType {
A = 1,
SOA = 6,
KEY = 24,
KEY = 24, //TODO: change to SIG
DNSKEY = 48,
OPT = 41,
ANY = 255,
}
@ -106,12 +107,3 @@ pub struct KeyRData {
pub signer: LabelString,
pub signature: Vec<u8>,
}
/// https://datatracker.ietf.org/doc/html/rfc4034#section-2
#[derive(Debug)]
pub struct DNSKeyRData {
pub flags: u16,
pub protocol: u8,
pub algorithm: u8,
pub public_key: Vec<u8>,
}

View file

@ -1,4 +1,3 @@
pub fn vec_equal<T: PartialEq>(vec1: &[T], vec2: &[T]) -> bool {
if vec1.len() != vec2.len() {
return false;