mirror of
https://github.com/ZeusWPI/ZNS.git
synced 2024-10-30 05:24:26 +01:00
SIG(0) authentication with zauth (ssh keys)
This commit is contained in:
parent
f5a1e21e86
commit
640aa93be2
11 changed files with 867 additions and 270 deletions
978
Cargo.lock
generated
978
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"]}
|
||||||
|
|
15
src/auth.rs
15
src/auth.rs
|
@ -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
49
src/authenticate.rs
Normal 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?,
|
||||||
|
)
|
||||||
|
}
|
|
@ -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))
|
||||||
|
|
|
@ -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};
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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,
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
51
src/sig.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue