mirror of
https://github.com/ZeusWPI/ZNS.git
synced 2024-10-30 05:24:26 +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::{
|
use crate::{
|
||||||
errors::DatabaseError,
|
errors::DatabaseError,
|
||||||
structs::{Class, Question, Type, RR},
|
structs::{Class, Type, RR},
|
||||||
};
|
};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
@ -99,16 +99,17 @@ pub async fn insert_into_database(rr: &RR) -> Result<(), DatabaseError> {
|
||||||
Ok(())
|
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 db_connection = &mut establish_connection();
|
||||||
let records = Record::get(
|
let records =
|
||||||
db_connection,
|
Record::get(db_connection, name.join("."), _type.into(), class.into()).map_err(|e| {
|
||||||
question.qname.join("."),
|
DatabaseError {
|
||||||
question.qtype.clone().into(),
|
|
||||||
question.qclass.clone().into(),
|
|
||||||
)
|
|
||||||
.map_err(|e| DatabaseError {
|
|
||||||
message: e.to_string(),
|
message: e.to_string(),
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(records
|
Ok(records
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::structs::RCODE;
|
||||||
|
|
||||||
pub struct DNSError {
|
pub struct DNSError {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub rcode: RCODE
|
pub rcode: RCODE,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DNSError {
|
impl fmt::Display for DNSError {
|
||||||
|
@ -27,7 +27,7 @@ impl fmt::Display for ParseError {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DatabaseError {
|
pub struct DatabaseError {
|
||||||
pub message: String
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DatabaseError {
|
impl fmt::Display for DatabaseError {
|
||||||
|
@ -77,7 +77,24 @@ where
|
||||||
fn from(value: E) -> Self {
|
fn from(value: E) -> Self {
|
||||||
DNSError {
|
DNSError {
|
||||||
message: value.into().to_string(),
|
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
|
response.header.arcount = 0; //TODO: fix this, handle unknown class values
|
||||||
|
|
||||||
for question in &message.question {
|
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 {
|
match answers {
|
||||||
Ok(rrs) => {
|
Ok(rrs) => {
|
||||||
|
|
|
@ -1,33 +1,30 @@
|
||||||
use reqwest::Error;
|
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>;
|
pub(super) async fn authenticate(
|
||||||
|
sig: &Sig,
|
||||||
type Result<T> = std::result::Result<T, AuthenticationError>;
|
zone: &Vec<String>,
|
||||||
|
) -> Result<bool, AuthenticationError> {
|
||||||
pub(super) async fn authenticate(sig: &Sig, zone: &Vec<String>) -> Result<bool> {
|
|
||||||
if zone.len() >= 4 {
|
if zone.len() >= 4 {
|
||||||
let username = &zone[zone.len() - 4]; // Should match: username.users.zeus.gent
|
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 ssh_verified = validate_ssh(username, sig).await?;
|
||||||
let key_split: Vec<&str> = key.split_ascii_whitespace().collect();
|
|
||||||
match key_split.len() {
|
if ssh_verified {
|
||||||
3 => {
|
Ok(true)
|
||||||
let key_encoded = key_split[1].to_string();
|
} else {
|
||||||
match key_split[0] {
|
Ok(validate_dnskey(zone, sig).await?)
|
||||||
"ssh-ed25519" => sig.verify(PublicKey::ED25519(key_encoded)),
|
|
||||||
_ => false,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
} else {
|
} else {
|
||||||
Err(AuthenticationError {
|
Err(AuthenticationError {
|
||||||
message: String::from("Invalid zone"),
|
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!(
|
Ok(reqwest::get(format!(
|
||||||
"{}/users/keys/{}",
|
"{}/users/keys/{}",
|
||||||
Config::get().zauth_url,
|
Config::get().zauth_url,
|
||||||
username
|
username
|
||||||
))
|
))
|
||||||
.await?
|
.await?
|
||||||
.json::<SSHKeys>()
|
.json::<Vec<String>>()
|
||||||
.await?)
|
.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;
|
use super::ResponseHandler;
|
||||||
|
|
||||||
mod authenticate;
|
mod authenticate;
|
||||||
|
mod dnskey;
|
||||||
mod sig;
|
mod sig;
|
||||||
|
|
||||||
pub(super) struct UpdateHandler {}
|
pub(super) struct UpdateHandler {}
|
||||||
|
|
|
@ -12,10 +12,6 @@ pub(super) struct Sig {
|
||||||
key_rdata: KeyRData,
|
key_rdata: KeyRData,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) enum PublicKey {
|
|
||||||
ED25519(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sig {
|
impl Sig {
|
||||||
pub fn new(rr: &RR, datagram: &[u8]) -> Result<Sig, ParseError> {
|
pub fn new(rr: &RR, datagram: &[u8]) -> Result<Sig, ParseError> {
|
||||||
let mut request = datagram[0..datagram.len() - 11 - rr.rdlength as usize].to_vec();
|
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 blob = BASE64_STANDARD.decode(key).unwrap();
|
||||||
|
|
||||||
let pkey = ring::signature::UnparsedPublicKey::new(
|
let pkey = ring::signature::UnparsedPublicKey::new(
|
||||||
|
@ -44,10 +49,4 @@ impl Sig {
|
||||||
pkey.verify(&self.raw_data, &self.key_rdata.signature)
|
pkey.verify(&self.raw_data, &self.key_rdata.signature)
|
||||||
.is_ok()
|
.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,
|
errors::ParseError,
|
||||||
reader::Reader,
|
reader::Reader,
|
||||||
structs::{
|
structs::{
|
||||||
Class, DNSKeyRData, Header, KeyRData, LabelString, Message, Opcode, Question, RRClass,
|
Class, Header, KeyRData, LabelString, Message, Opcode, Question, RRClass, RRType, Type, RR,
|
||||||
RRType, Type, RR,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,12 +42,14 @@ impl From<Class> for u16 {
|
||||||
|
|
||||||
impl From<u16> for Type {
|
impl From<u16> for Type {
|
||||||
fn from(value: u16) -> Self {
|
fn from(value: u16) -> Self {
|
||||||
|
//TODO: use macro
|
||||||
match value {
|
match value {
|
||||||
x if x == RRType::A as u16 => Type::Type(RRType::A),
|
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::OPT as u16 => Type::Type(RRType::OPT),
|
||||||
x if x == RRType::SOA as u16 => Type::Type(RRType::SOA),
|
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::ANY as u16 => Type::Type(RRType::SOA),
|
||||||
x if x == RRType::KEY as u16 => Type::Type(RRType::KEY),
|
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),
|
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 {
|
pub enum RRType {
|
||||||
A = 1,
|
A = 1,
|
||||||
SOA = 6,
|
SOA = 6,
|
||||||
KEY = 24,
|
KEY = 24, //TODO: change to SIG
|
||||||
|
DNSKEY = 48,
|
||||||
OPT = 41,
|
OPT = 41,
|
||||||
ANY = 255,
|
ANY = 255,
|
||||||
}
|
}
|
||||||
|
@ -106,12 +107,3 @@ pub struct KeyRData {
|
||||||
pub signer: LabelString,
|
pub signer: LabelString,
|
||||||
pub signature: Vec<u8>,
|
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 {
|
pub fn vec_equal<T: PartialEq>(vec1: &[T], vec2: &[T]) -> bool {
|
||||||
if vec1.len() != vec2.len() {
|
if vec1.len() != vec2.len() {
|
||||||
return false;
|
return false;
|
||||||
|
|
Loading…
Reference in a new issue