mirror of
https://github.com/ZeusWPI/ZNS.git
synced 2024-11-24 14:11:10 +01:00
add DNSKEY authentication support
This commit is contained in:
parent
4469b6f0b5
commit
c6437e6901
10 changed files with 138 additions and 83 deletions
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
36
src/handlers/update/dnskey.rs
Normal file
36
src/handlers/update/dnskey.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ use self::sig::Sig;
|
|||
use super::ResponseHandler;
|
||||
|
||||
mod authenticate;
|
||||
mod dnskey;
|
||||
mod sig;
|
||||
|
||||
pub(super) struct UpdateHandler {}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
pub fn vec_equal<T: PartialEq>(vec1: &[T], vec2: &[T]) -> bool {
|
||||
if vec1.len() != vec2.len() {
|
||||
return false;
|
||||
|
|
Loading…
Reference in a new issue