10
0
Fork 0
mirror of https://github.com/ZeusWPI/ZNS.git synced 2024-10-30 05:24:26 +01:00

check signature algorithm beforehand and add RSASHA256 support

This commit is contained in:
Xander Bil 2024-06-20 22:58:50 +02:00
parent 1161d34194
commit 6ac9f2f36e
No known key found for this signature in database
GPG key ID: EC9706B54A278598
12 changed files with 196 additions and 134 deletions

47
Cargo.lock generated
View file

@ -435,6 +435,19 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "int-enum"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a37a9c11c6ecfec8b9bed97337dfecff3686d02ba8f52e8addad2829d047128"
dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
"version_check",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.9.0" version = "2.9.0"
@ -647,13 +660,26 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.78" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "proc-macro2-diagnostics"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"version_check",
"yansi",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.35" version = "1.0.35"
@ -869,9 +895,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.49" version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1079,6 +1105,12 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"
@ -1312,6 +1344,12 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]] [[package]]
name = "zeusns" name = "zeusns"
version = "0.1.0" version = "0.1.0"
@ -1320,6 +1358,7 @@ dependencies = [
"base64", "base64",
"diesel", "diesel",
"dotenvy", "dotenvy",
"int-enum",
"reqwest", "reqwest",
"ring", "ring",
"tokio", "tokio",

View file

@ -3,7 +3,6 @@ name = "zeusns"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
diesel = { version = "2.1.4", features = ["postgres"] } diesel = { version = "2.1.4", features = ["postgres"] }
dotenvy = "0.15" dotenvy = "0.15"
@ -12,3 +11,4 @@ ring = "0.17.8"
base64 = "0.22.0" base64 = "0.22.0"
reqwest = {version = "0.12.4", features = ["json","default"]} reqwest = {version = "0.12.4", features = ["json","default"]}
asn1 = "0.16.2" asn1 = "0.16.2"
int-enum = "1.1"

View file

@ -6,7 +6,7 @@ use crate::{
use super::ResponseHandler; use super::ResponseHandler;
pub(super) struct QueryHandler {} pub struct QueryHandler {}
impl ResponseHandler for QueryHandler { impl ResponseHandler for QueryHandler {
async fn handle(message: &Message, _raw: &[u8]) -> Result<Message, DNSError> { async fn handle(message: &Message, _raw: &[u8]) -> Result<Message, DNSError> {

View file

@ -1,5 +1,3 @@
use base64::prelude::*;
use crate::{ use crate::{
config::Config, config::Config,
db::models::get_from_database, db::models::get_from_database,
@ -9,16 +7,9 @@ use crate::{
structs::{Class, RRClass, RRType, Type}, structs::{Class, RRClass, RRType, Type},
}; };
use super::{ use super::{dnskey::DNSKeyRData, pubkeys::PublicKeyError, sig::Sig};
dnskey::DNSKeyRData,
pubkeys::{Ed25519PublicKey, PublicKey, PublicKeyError, RsaPublicKey, SSH_ED25519, SSH_RSA},
sig::Sig,
};
pub(super) async fn authenticate( pub async fn authenticate(sig: &Sig, zone: &Vec<String>) -> Result<bool, AuthenticationError> {
sig: &Sig,
zone: &Vec<String>,
) -> Result<bool, AuthenticationError> {
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
@ -36,7 +27,7 @@ pub(super) async fn authenticate(
} }
} }
async fn validate_ssh(username: &String, sig: &Sig) -> Result<bool, PublicKeyError> { async fn validate_ssh(username: &String, sig: &Sig) -> Result<bool, reqwest::Error> {
Ok(reqwest::get(format!( Ok(reqwest::get(format!(
"{}/users/keys/{}", "{}/users/keys/{}",
Config::get().zauth_url, Config::get().zauth_url,
@ -46,18 +37,7 @@ async fn validate_ssh(username: &String, sig: &Sig) -> Result<bool, PublicKeyErr
.json::<Vec<String>>() .json::<Vec<String>>()
.await? .await?
.iter() .iter()
.any(|key| { .any(|key| sig.verify_ssh(&key).is_ok_and(|b| b)))
let key_split: Vec<&str> = key.split_ascii_whitespace().collect();
let bin = BASE64_STANDARD.decode(key_split[1]).unwrap();
match key_split[0] {
//TODO: do something with error, debugging?
SSH_ED25519 => {
Ed25519PublicKey::from_openssh(&bin).is_ok_and(|pubkey| sig.verify(pubkey))
}
SSH_RSA => RsaPublicKey::from_openssh(&bin).is_ok_and(|pubkey| sig.verify(pubkey)),
_ => false,
}
}))
} }
async fn validate_dnskey(zone: &Vec<String>, sig: &Sig) -> Result<bool, DatabaseError> { async fn validate_dnskey(zone: &Vec<String>, sig: &Sig) -> Result<bool, DatabaseError> {
@ -68,20 +48,11 @@ async fn validate_dnskey(zone: &Vec<String>, sig: &Sig) -> Result<bool, Database
.any(|rr| { .any(|rr| {
let mut reader = Reader::new(&rr.rdata); let mut reader = Reader::new(&rr.rdata);
DNSKeyRData::from_bytes(&mut reader) DNSKeyRData::from_bytes(&mut reader)
.map(|key| key.verify(sig)) .is_ok_and(|dnskey| sig.verify_dnskey(dnskey).is_ok_and(|b| b))
.is_ok_and(|b| b)
}), }),
) )
} }
impl From<reqwest::Error> for PublicKeyError {
fn from(value: reqwest::Error) -> Self {
PublicKeyError {
message: format!("Reqwest Error: {}", value.to_string()),
}
}
}
impl From<PublicKeyError> for AuthenticationError { impl From<PublicKeyError> for AuthenticationError {
fn from(value: PublicKeyError) -> Self { fn from(value: PublicKeyError) -> Self {
AuthenticationError { AuthenticationError {

View file

@ -1,16 +1,13 @@
use crate::{errors::ParseError, parser::FromBytes, reader::Reader}; use crate::{errors::ParseError, parser::FromBytes, reader::Reader};
use super::{ use super::sig::Algorithm;
pubkeys::{Ed25519PublicKey, PublicKey, RsaPublicKey},
sig::Sig,
};
/// https://datatracker.ietf.org/doc/html/rfc4034#section-2 /// https://datatracker.ietf.org/doc/html/rfc4034#section-2
#[derive(Debug)] #[derive(Debug)]
pub(super) struct DNSKeyRData { pub struct DNSKeyRData {
pub flags: u16, pub flags: u16,
pub protocol: u8, pub protocol: u8,
pub algorithm: u8, pub algorithm: Algorithm,
pub public_key: Vec<u8>, pub public_key: Vec<u8>,
} }
@ -20,21 +17,8 @@ impl FromBytes for DNSKeyRData {
Ok(DNSKeyRData { Ok(DNSKeyRData {
flags: reader.read_u16()?, flags: reader.read_u16()?,
protocol: reader.read_u8()?, protocol: reader.read_u8()?,
algorithm: reader.read_u8()?, algorithm: Algorithm::from(reader.read_u8()?)?,
public_key: reader.read(reader.unread_bytes())?, public_key: reader.read(reader.unread_bytes())?,
}) })
} }
} }
impl DNSKeyRData {
pub fn verify(&self, sig: &Sig) -> bool {
match self.algorithm {
10 => {
RsaPublicKey::from_dnskey(&self.public_key).is_ok_and(|pubkey| sig.verify(pubkey))
}
15 => Ed25519PublicKey::from_dnskey(&self.public_key)
.is_ok_and(|pubkey| sig.verify(pubkey)),
_ => false,
}
}
}

View file

@ -14,7 +14,7 @@ mod dnskey;
mod pubkeys; mod pubkeys;
mod sig; mod sig;
pub(super) struct UpdateHandler {} pub struct UpdateHandler {}
impl ResponseHandler for UpdateHandler { impl ResponseHandler for UpdateHandler {
async fn handle(message: &Message, raw: &[u8]) -> Result<Message, crate::errors::DNSError> { async fn handle(message: &Message, raw: &[u8]) -> Result<Message, crate::errors::DNSError> {

View file

@ -1,6 +1,6 @@
use ring::signature; use ring::signature;
use crate::reader::Reader; use crate::{handlers::update::sig::Algorithm, reader::Reader};
use super::{PublicKey, PublicKeyError, SSH_ED25519}; use super::{PublicKey, PublicKeyError, SSH_ED25519};
@ -28,7 +28,12 @@ impl PublicKey for Ed25519PublicKey {
Ok(Ed25519PublicKey { data: key.to_vec() }) Ok(Ed25519PublicKey { data: key.to_vec() })
} }
fn verify(&self, data: &[u8], signature: &[u8]) -> Result<bool, PublicKeyError> { fn verify(
&self,
data: &[u8],
signature: &[u8],
_algorithm: &Algorithm,
) -> Result<bool, PublicKeyError> {
let pkey = ring::signature::UnparsedPublicKey::new(&signature::ED25519, &self.data); let pkey = ring::signature::UnparsedPublicKey::new(&signature::ED25519, &self.data);
Ok(pkey.verify(data, signature).is_ok()) Ok(pkey.verify(data, signature).is_ok())

View file

@ -3,13 +3,13 @@ mod rsa;
use core::fmt; use core::fmt;
use std::str::from_utf8; use std::str::from_utf8;
use base64::prelude::*;
use crate::{errors::ReaderError, reader::Reader}; use crate::{errors::ReaderError, reader::Reader};
pub use self::ed25519::Ed25519PublicKey; pub use self::ed25519::Ed25519PublicKey;
pub use self::rsa::RsaPublicKey; pub use self::rsa::RsaPublicKey;
use super::sig::Algorithm;
#[derive(Debug)] #[derive(Debug)]
pub struct PublicKeyError { pub struct PublicKeyError {
pub message: String, pub message: String,
@ -60,5 +60,10 @@ pub trait PublicKey {
where where
Self: Sized; Self: Sized;
fn verify(&self, data: &[u8], signature: &[u8]) -> Result<bool, PublicKeyError>; fn verify(
&self,
data: &[u8],
signature: &[u8],
algorithm: &Algorithm,
) -> Result<bool, PublicKeyError>;
} }

View file

@ -1,6 +1,6 @@
use ring::signature; use ring::signature;
use crate::reader::Reader; use crate::{handlers::update::sig::Algorithm, reader::Reader};
use super::{PublicKey, PublicKeyError, SSH_RSA}; use super::{PublicKey, PublicKeyError, SSH_RSA};
@ -29,7 +29,12 @@ impl PublicKey for RsaPublicKey {
Ok(RsaPublicKey { e, n }) Ok(RsaPublicKey { e, n })
} }
fn verify(&self, data: &[u8], signature: &[u8]) -> Result<bool, PublicKeyError> { fn verify(
&self,
data: &[u8],
signature: &[u8],
algorithm: &Algorithm,
) -> Result<bool, PublicKeyError> {
let result = asn1::write_single(&RsaAsn1 { let result = asn1::write_single(&RsaAsn1 {
n: asn1::BigInt::new(&self.n), n: asn1::BigInt::new(&self.n),
e: asn1::BigInt::new(&self.e), e: asn1::BigInt::new(&self.e),
@ -38,8 +43,15 @@ impl PublicKey for RsaPublicKey {
message: format!("Verify Error: {}", e), message: format!("Verify Error: {}", e),
})?; })?;
let pkey = let signature_type = match algorithm {
ring::signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA512, result); Algorithm::RSASHA512 => Ok(&signature::RSA_PKCS1_2048_8192_SHA512),
Algorithm::RSASHA256 => Ok(&signature::RSA_PKCS1_2048_8192_SHA256),
_ => Err(PublicKeyError {
message: format!("RsaPublicKey: invalid verify algorithm",),
}),
}?;
let pkey = ring::signature::UnparsedPublicKey::new(signature_type, result);
Ok(pkey.verify(data, signature).is_ok()) Ok(pkey.verify(data, signature).is_ok())
} }

View file

@ -1,24 +1,85 @@
use base64::prelude::*;
use int_enum::IntEnum;
use crate::{ use crate::{
errors::ParseError, errors::ParseError,
parser::FromBytes, parser::FromBytes,
reader::Reader, reader::Reader,
structs::{KeyRData, RR}, structs::{LabelString, RR},
}; };
use super::pubkeys::PublicKey; use super::{
dnskey::DNSKeyRData,
pubkeys::{Ed25519PublicKey, PublicKey, PublicKeyError, RsaPublicKey, SSH_ED25519, SSH_RSA},
};
pub(super) struct Sig { pub struct Sig {
raw_data: Vec<u8>, raw_data: Vec<u8>,
key_rdata: KeyRData, key_rdata: SigRData,
}
#[allow(dead_code)]
struct SigRData {
type_covered: u16,
algo: Algorithm,
labels: u8,
original_ttl: u32,
signature_expiration: u32,
signature_inception: u32,
key_tag: u16,
signer: LabelString,
signature: Vec<u8>,
}
/// https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
#[repr(u8)]
#[derive(IntEnum, Debug, PartialEq)]
pub enum Algorithm {
ED25519 = 15,
RSASHA512 = 10,
RSASHA256 = 8,
}
impl Algorithm {
pub fn from(value: u8) -> Result<Self, ParseError> {
Algorithm::try_from(value).map_err(|a| ParseError {
// TODO: Should respond with error code refused or notimpl
object: String::from("Algorithm"),
message: format!("Usupported algorithm: {}", a),
})
}
}
impl FromBytes for SigRData {
fn from_bytes(reader: &mut Reader) -> Result<Self, ParseError> {
if reader.unread_bytes() < 18 {
Err(ParseError {
object: String::from("KeyRData"),
message: String::from("invalid rdata"),
})
} else {
Ok(SigRData {
type_covered: reader.read_u16()?,
algo: Algorithm::from(reader.read_u8()?)?,
labels: reader.read_u8()?,
original_ttl: reader.read_u32()?,
signature_expiration: reader.read_u32()?,
signature_inception: reader.read_u32()?,
key_tag: reader.read_u16()?,
signer: LabelString::from_bytes(reader)?,
signature: reader.read(reader.unread_bytes())?,
})
}
}
} }
impl Sig { impl Sig {
pub fn new(rr: &RR, datagram: &[u8]) -> Result<Sig, ParseError> { pub fn new(rr: &RR, datagram: &[u8]) -> Result<Self, 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();
request[11] -= 1; // Decrease arcount request[11] -= 1; // Decrease arcount
let mut reader = Reader::new(&rr.rdata); let mut reader = Reader::new(&rr.rdata);
let key_rdata = KeyRData::from_bytes(&mut reader)?; let key_rdata = SigRData::from_bytes(&mut reader)?;
let mut raw_data = rr.rdata[0..rr.rdata.len() - key_rdata.signature.len()].to_vec(); let mut raw_data = rr.rdata[0..rr.rdata.len() - key_rdata.signature.len()].to_vec();
raw_data.extend(request); raw_data.extend(request);
@ -29,8 +90,37 @@ impl Sig {
}) })
} }
pub fn verify(&self, key: impl PublicKey) -> bool { fn verify(&self, key: impl PublicKey) -> Result<bool, PublicKeyError> {
key.verify(&self.raw_data, &self.key_rdata.signature) key.verify(
.is_ok_and(|b| b) &self.raw_data,
&self.key_rdata.signature,
&self.key_rdata.algo,
)
}
pub fn verify_ssh(&self, key: &str) -> Result<bool, PublicKeyError> {
let key_split: Vec<&str> = key.split_ascii_whitespace().collect();
let bin = BASE64_STANDARD.decode(key_split[1]).unwrap();
match (key_split[0], &self.key_rdata.algo) {
(SSH_ED25519, Algorithm::ED25519) => self.verify(Ed25519PublicKey::from_openssh(&bin)?),
(SSH_RSA, Algorithm::RSASHA512 | Algorithm::RSASHA256) => {
self.verify(RsaPublicKey::from_openssh(&bin)?)
}
_ => Ok(false),
}
}
pub fn verify_dnskey(&self, key: DNSKeyRData) -> Result<bool, PublicKeyError> {
if self.key_rdata.algo != key.algorithm {
Ok(false)
} else {
match self.key_rdata.algo {
Algorithm::RSASHA512 | Algorithm::RSASHA256 => {
self.verify(RsaPublicKey::from_dnskey(&key.public_key)?)
}
Algorithm::ED25519 => self.verify(Ed25519PublicKey::from_dnskey(&key.public_key)?),
}
}
} }
} }

View file

@ -3,9 +3,7 @@ use std::{mem::size_of, vec};
use crate::{ use crate::{
errors::ParseError, errors::ParseError,
reader::Reader, reader::Reader,
structs::{ structs::{Class, Header, LabelString, Message, Opcode, Question, RRClass, RRType, Type, RR},
Class, Header, KeyRData, LabelString, Message, Opcode, Question, RRClass, RRType, Type, RR,
},
}; };
type Result<T> = std::result::Result<T, ParseError>; type Result<T> = std::result::Result<T, ParseError>;
@ -42,26 +40,18 @@ 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 RRType::try_from(value) {
match value { Ok(rrtype) => Type::Type(rrtype),
x if x == RRType::A as u16 => Type::Type(RRType::A), Err(x) => Type::Other(x),
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),
} }
} }
} }
impl From<u16> for Class { impl From<u16> for Class {
fn from(value: u16) -> Self { fn from(value: u16) -> Self {
match value { match RRClass::try_from(value) {
x if x == RRClass::IN as u16 => Class::Class(RRClass::IN), Ok(rrclass) => Class::Class(rrclass),
x if x == RRClass::ANY as u16 => Class::Class(RRClass::ANY), Err(x) => Class::Other(x),
x if x == RRClass::NONE as u16 => Class::Class(RRClass::NONE),
x => Class::Other(x),
} }
} }
} }
@ -305,26 +295,3 @@ impl ToBytes for Message {
result result
} }
} }
impl FromBytes for KeyRData {
fn from_bytes(reader: &mut Reader) -> Result<Self> {
if reader.unread_bytes() < 18 {
Err(ParseError {
object: String::from("KeyRData"),
message: String::from("invalid rdata"),
})
} else {
Ok(KeyRData {
type_covered: reader.read_u16()?,
algo: reader.read_u8()?,
labels: reader.read_u8()?,
original_ttl: reader.read_u32()?,
signature_expiration: reader.read_u32()?,
signature_inception: reader.read_u32()?,
key_tag: reader.read_u16()?,
signer: LabelString::from_bytes(reader)?,
signature: reader.read(reader.unread_bytes())?,
})
}
}
}

View file

@ -1,3 +1,5 @@
use int_enum::IntEnum;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Type { pub enum Type {
Type(RRType), Type(RRType),
@ -5,7 +7,7 @@ pub enum Type {
} }
#[repr(u16)] #[repr(u16)]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, IntEnum)]
pub enum RRType { pub enum RRType {
A = 1, A = 1,
SOA = 6, SOA = 6,
@ -22,7 +24,7 @@ pub enum Class {
} }
#[repr(u16)] #[repr(u16)]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, IntEnum)]
pub enum RRClass { pub enum RRClass {
IN = 1, IN = 1,
NONE = 254, NONE = 254,
@ -94,16 +96,3 @@ pub struct OptRR {
} }
pub type LabelString = Vec<String>; pub type LabelString = Vec<String>;
#[derive(Debug)]
pub struct KeyRData {
pub type_covered: u16,
pub algo: u8,
pub labels: u8,
pub original_ttl: u32,
pub signature_expiration: u32,
pub signature_inception: u32,
pub key_tag: u16,
pub signer: LabelString,
pub signature: Vec<u8>,
}