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:
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"] }
|
||||
dotenvy = "0.15"
|
||||
tokio = {version = "1.36.0", features = ["macros","rt-multi-thread","net"], default-features = false}
|
||||
sshkeys = "0.3.2"
|
||||
ring = "0.17.8"
|
||||
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::sqlite::SqliteConnection;
|
||||
use dotenvy::dotenv;
|
||||
use std::env;
|
||||
|
||||
pub fn establish_connection() -> SqliteConnection {
|
||||
dotenv().ok();
|
||||
|
||||
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
SqliteConnection::establish(&database_url)
|
||||
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
errors::DatabaseError,
|
||||
structs::{Class, Question, Type, RR},
|
||||
};
|
||||
use diesel::{dsl, prelude::*};
|
||||
use diesel::prelude::*;
|
||||
|
||||
use self::schema::records::{self};
|
||||
|
||||
|
|
|
@ -33,3 +33,14 @@ impl fmt::Display for DatabaseError {
|
|||
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 dotenvy::dotenv;
|
||||
|
||||
use crate::resolver::resolver_listener_loop;
|
||||
|
||||
mod db;
|
||||
mod errors;
|
||||
mod parser;
|
||||
mod resolver;
|
||||
mod auth;
|
||||
mod structs;
|
||||
mod utils;
|
||||
mod sig;
|
||||
mod authenticate;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
dotenv().ok();
|
||||
|
||||
let resolver_add = SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||
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
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -4,11 +4,12 @@ use std::sync::Arc;
|
|||
|
||||
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::errors::ParseError;
|
||||
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;
|
||||
|
||||
const MAX_DATAGRAM_SIZE: usize = 4096;
|
||||
|
@ -68,17 +69,9 @@ async fn handle_update(message: Message, bytes: &[u8]) -> Message {
|
|||
//TODO: this code is ugly
|
||||
let last = message.additional.last();
|
||||
if last.is_some() && last.unwrap()._type == Type::Type(RRType::KEY) {
|
||||
let rr = last.unwrap();
|
||||
let mut request = bytes[0..bytes.len() - 11 - rr.rdlength as usize].to_vec();
|
||||
request[11] -= 1; // Decrease arcount
|
||||
let sig = Sig::new(last.unwrap(), bytes);
|
||||
|
||||
let mut i = 0;
|
||||
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()) {
|
||||
if !authenticate(&sig, &zone.qname).await.is_ok_and(|x| x) {
|
||||
response.header.flags = set_response_flags(response.header.flags, RCODE::NOTAUTH);
|
||||
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