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

SIG(0) authentication with zauth (ssh keys)

This commit is contained in:
Xander Bil 2024-06-04 23:48:58 +02:00
parent f5a1e21e86
commit 640aa93be2
No known key found for this signature in database
GPG key ID: EC9706B54A278598
11 changed files with 867 additions and 270 deletions

978
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,6 @@ edition = "2021"
diesel = { version = "2.1.4", features = ["sqlite"] } diesel = { version = "2.1.4", features = ["sqlite"] }
dotenvy = "0.15" dotenvy = "0.15"
tokio = {version = "1.36.0", features = ["macros","rt-multi-thread","net"], default-features = false} tokio = {version = "1.36.0", features = ["macros","rt-multi-thread","net"], default-features = false}
sshkeys = "0.3.2"
ring = "0.17.8" ring = "0.17.8"
base64 = "0.22.0" base64 = "0.22.0"
ed25519-dalek = "2.1.1" reqwest = {version = "0.12.4", features = ["json","default"]}

View file

@ -1,15 +0,0 @@
use std::fs::read_to_string;
use base64::prelude::*;
pub fn verify(signature: &[u8], message: &[u8]) -> bool {
let str = read_to_string("dns.pub").unwrap(); //TODO: pub ssh key use zauth
let key_split: Vec<&str> = str.split_ascii_whitespace().collect();
let blob = BASE64_STANDARD.decode(key_split[1]).unwrap();
let key = ring::signature::UnparsedPublicKey::new(&ring::signature::ED25519, &blob.as_slice()[19..]);
return key.verify(&message, signature.as_ref()).is_ok();
}

49
src/authenticate.rs Normal file
View file

@ -0,0 +1,49 @@
use std::env;
use reqwest::Error;
use crate::{
errors::AuthenticationError,
sig::{PublicKey, Sig},
};
type SSHKeys = Vec<String>;
type Result<T> = std::result::Result<T, AuthenticationError>;
pub async fn authenticate(sig: &Sig, zone: &Vec<String>) -> Result<bool> {
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,
}
}
_ => false,
}
}))
} else {
Err(AuthenticationError {
message: String::from("Invalid zone"),
})
}
}
async fn get_keys(username: &String) -> std::result::Result<SSHKeys, Error> {
let zauth_url = env::var("ZAUTH_URL").expect("ZAUTH_URL must be set");
Ok(
reqwest::get(format!("{}/users/keys/{}", zauth_url, username))
.await?
.json::<SSHKeys>()
.await?,
)
}

View file

@ -1,11 +1,8 @@
use diesel::prelude::*; use diesel::prelude::*;
use diesel::sqlite::SqliteConnection; use diesel::sqlite::SqliteConnection;
use dotenvy::dotenv;
use std::env; use std::env;
pub fn establish_connection() -> SqliteConnection { pub fn establish_connection() -> SqliteConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
SqliteConnection::establish(&database_url) SqliteConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) .unwrap_or_else(|_| panic!("Error connecting to {}", database_url))

View file

@ -2,7 +2,7 @@ use crate::{
errors::DatabaseError, errors::DatabaseError,
structs::{Class, Question, Type, RR}, structs::{Class, Question, Type, RR},
}; };
use diesel::{dsl, prelude::*}; use diesel::prelude::*;
use self::schema::records::{self}; use self::schema::records::{self};

View file

@ -33,3 +33,14 @@ impl fmt::Display for DatabaseError {
write!(f, "Database Error: {}", self.message) write!(f, "Database Error: {}", self.message)
} }
} }
#[derive(Debug)]
pub struct AuthenticationError {
pub message: String,
}
impl fmt::Display for AuthenticationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Authentication Error: {}", self.message)
}
}

View file

@ -1,17 +1,21 @@
use std::{error::Error, net::SocketAddr}; use std::{error::Error, net::SocketAddr};
use dotenvy::dotenv;
use crate::resolver::resolver_listener_loop; use crate::resolver::resolver_listener_loop;
mod db; mod db;
mod errors; mod errors;
mod parser; mod parser;
mod resolver; mod resolver;
mod auth;
mod structs; mod structs;
mod utils; mod utils;
mod sig;
mod authenticate;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> { async fn main() -> Result<(), Box<dyn Error>> {
dotenv().ok();
let resolver_add = SocketAddr::from(([127, 0, 0, 1], 8080)); let resolver_add = SocketAddr::from(([127, 0, 0, 1], 8080));
let _ = tokio::join!( let _ = tokio::join!(

View file

@ -368,7 +368,7 @@ impl FromBytes for KeyRData {
} }
} }
fn to_bytes(s: Self) -> Vec<u8> fn to_bytes(_: Self) -> Vec<u8>
where where
Self: Sized, Self: Sized,
{ {

View file

@ -4,11 +4,12 @@ use std::sync::Arc;
use tokio::net::UdpSocket; use tokio::net::UdpSocket;
use crate::auth::verify; use crate::authenticate::authenticate;
use crate::db::models::{delete_from_database, get_from_database, insert_into_database}; use crate::db::models::{delete_from_database, get_from_database, insert_into_database};
use crate::errors::ParseError; use crate::errors::ParseError;
use crate::parser::FromBytes; use crate::parser::FromBytes;
use crate::structs::{Class, Header, KeyRData, Message, Opcode, RRClass, RRType, Type, RCODE}; use crate::sig::Sig;
use crate::structs::{Class, Header, Message, Opcode, RRClass, RRType, Type, RCODE};
use crate::utils::vec_equal; use crate::utils::vec_equal;
const MAX_DATAGRAM_SIZE: usize = 4096; const MAX_DATAGRAM_SIZE: usize = 4096;
@ -68,17 +69,9 @@ async fn handle_update(message: Message, bytes: &[u8]) -> Message {
//TODO: this code is ugly //TODO: this code is ugly
let last = message.additional.last(); let last = message.additional.last();
if last.is_some() && last.unwrap()._type == Type::Type(RRType::KEY) { if last.is_some() && last.unwrap()._type == Type::Type(RRType::KEY) {
let rr = last.unwrap(); let sig = Sig::new(last.unwrap(), bytes);
let mut request = bytes[0..bytes.len() - 11 - rr.rdlength as usize].to_vec();
request[11] -= 1; // Decrease arcount
let mut i = 0; if !authenticate(&sig, &zone.qname).await.is_ok_and(|x| x) {
let key = KeyRData::from_bytes(&rr.rdata, &mut i).unwrap();
let mut data = rr.rdata[0..i].to_vec();
data.extend(request);
if !verify(&key.signature, &data.as_slice()) {
response.header.flags = set_response_flags(response.header.flags, RCODE::NOTAUTH); response.header.flags = set_response_flags(response.header.flags, RCODE::NOTAUTH);
return response; return response;
} }

51
src/sig.rs Normal file
View file

@ -0,0 +1,51 @@
use base64::prelude::*;
use crate::{
parser::FromBytes,
structs::{KeyRData, RR},
};
pub struct Sig {
raw_data: Vec<u8>,
key_rdata: KeyRData,
}
pub enum PublicKey {
ED25519(String),
}
impl Sig {
pub fn new(rr: &RR, datagram: &[u8]) -> Sig {
let mut request = datagram[0..datagram.len() - 11 - rr.rdlength as usize].to_vec();
request[11] -= 1; // Decrease arcount
let mut i = 0;
let key_rdata = KeyRData::from_bytes(&rr.rdata, &mut i).unwrap();
let mut raw_data = rr.rdata[0..i].to_vec();
raw_data.extend(request);
Sig {
raw_data,
key_rdata,
}
}
fn verify_ed25519(&self, key: String) -> bool {
let blob = BASE64_STANDARD.decode(key).unwrap();
let pkey = ring::signature::UnparsedPublicKey::new(
&ring::signature::ED25519,
&blob.as_slice()[19..],
);
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),
}
}
}