From 6ac9f2f36e3da0e75f2921ce340fd6cdf1dd1aab Mon Sep 17 00:00:00 2001 From: Xander Bil Date: Thu, 20 Jun 2024 22:58:50 +0200 Subject: [PATCH] check signature algorithm beforehand and add RSASHA256 support --- Cargo.lock | 47 ++++++++++- Cargo.toml | 2 +- src/handlers/query.rs | 2 +- src/handlers/update/authenticate.rs | 39 ++------- src/handlers/update/dnskey.rs | 24 +----- src/handlers/update/mod.rs | 2 +- src/handlers/update/pubkeys/ed25519.rs | 9 ++- src/handlers/update/pubkeys/mod.rs | 11 ++- src/handlers/update/pubkeys/rsa.rs | 20 ++++- src/handlers/update/sig.rs | 108 ++++++++++++++++++++++--- src/parser.rs | 47 ++--------- src/structs.rs | 19 +---- 12 files changed, 196 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01de336..993801a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,6 +435,19 @@ dependencies = [ "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]] name = "ipnet" version = "2.9.0" @@ -647,13 +660,26 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "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]] name = "quote" version = "1.0.35" @@ -869,9 +895,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "syn" -version = "2.0.49" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -1079,6 +1105,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "want" version = "0.3.1" @@ -1312,6 +1344,12 @@ dependencies = [ "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]] name = "zeusns" version = "0.1.0" @@ -1320,6 +1358,7 @@ dependencies = [ "base64", "diesel", "dotenvy", + "int-enum", "reqwest", "ring", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 574c5be..b4a374b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ name = "zeusns" version = "0.1.0" edition = "2021" - [dependencies] diesel = { version = "2.1.4", features = ["postgres"] } dotenvy = "0.15" @@ -12,3 +11,4 @@ ring = "0.17.8" base64 = "0.22.0" reqwest = {version = "0.12.4", features = ["json","default"]} asn1 = "0.16.2" +int-enum = "1.1" diff --git a/src/handlers/query.rs b/src/handlers/query.rs index 03c3ef0..d9bec21 100644 --- a/src/handlers/query.rs +++ b/src/handlers/query.rs @@ -6,7 +6,7 @@ use crate::{ use super::ResponseHandler; -pub(super) struct QueryHandler {} +pub struct QueryHandler {} impl ResponseHandler for QueryHandler { async fn handle(message: &Message, _raw: &[u8]) -> Result { diff --git a/src/handlers/update/authenticate.rs b/src/handlers/update/authenticate.rs index 4faf112..969a3d6 100644 --- a/src/handlers/update/authenticate.rs +++ b/src/handlers/update/authenticate.rs @@ -1,5 +1,3 @@ -use base64::prelude::*; - use crate::{ config::Config, db::models::get_from_database, @@ -9,16 +7,9 @@ use crate::{ structs::{Class, RRClass, RRType, Type}, }; -use super::{ - dnskey::DNSKeyRData, - pubkeys::{Ed25519PublicKey, PublicKey, PublicKeyError, RsaPublicKey, SSH_ED25519, SSH_RSA}, - sig::Sig, -}; +use super::{dnskey::DNSKeyRData, pubkeys::PublicKeyError, sig::Sig}; -pub(super) async fn authenticate( - sig: &Sig, - zone: &Vec, -) -> Result { +pub async fn authenticate(sig: &Sig, zone: &Vec) -> Result { if zone.len() >= 4 { 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 { +async fn validate_ssh(username: &String, sig: &Sig) -> Result { Ok(reqwest::get(format!( "{}/users/keys/{}", Config::get().zauth_url, @@ -46,18 +37,7 @@ async fn validate_ssh(username: &String, sig: &Sig) -> Result>() .await? .iter() - .any(|key| { - 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, - } - })) + .any(|key| sig.verify_ssh(&key).is_ok_and(|b| b))) } async fn validate_dnskey(zone: &Vec, sig: &Sig) -> Result { @@ -68,20 +48,11 @@ async fn validate_dnskey(zone: &Vec, sig: &Sig) -> Result for PublicKeyError { - fn from(value: reqwest::Error) -> Self { - PublicKeyError { - message: format!("Reqwest Error: {}", value.to_string()), - } - } -} - impl From for AuthenticationError { fn from(value: PublicKeyError) -> Self { AuthenticationError { diff --git a/src/handlers/update/dnskey.rs b/src/handlers/update/dnskey.rs index e58327a..39af8e5 100644 --- a/src/handlers/update/dnskey.rs +++ b/src/handlers/update/dnskey.rs @@ -1,16 +1,13 @@ use crate::{errors::ParseError, parser::FromBytes, reader::Reader}; -use super::{ - pubkeys::{Ed25519PublicKey, PublicKey, RsaPublicKey}, - sig::Sig, -}; +use super::sig::Algorithm; /// https://datatracker.ietf.org/doc/html/rfc4034#section-2 #[derive(Debug)] -pub(super) struct DNSKeyRData { +pub struct DNSKeyRData { pub flags: u16, pub protocol: u8, - pub algorithm: u8, + pub algorithm: Algorithm, pub public_key: Vec, } @@ -20,21 +17,8 @@ impl FromBytes for DNSKeyRData { Ok(DNSKeyRData { flags: reader.read_u16()?, protocol: reader.read_u8()?, - algorithm: reader.read_u8()?, + algorithm: Algorithm::from(reader.read_u8()?)?, 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, - } - } -} diff --git a/src/handlers/update/mod.rs b/src/handlers/update/mod.rs index ede0c5b..6df813e 100644 --- a/src/handlers/update/mod.rs +++ b/src/handlers/update/mod.rs @@ -14,7 +14,7 @@ mod dnskey; mod pubkeys; mod sig; -pub(super) struct UpdateHandler {} +pub struct UpdateHandler {} impl ResponseHandler for UpdateHandler { async fn handle(message: &Message, raw: &[u8]) -> Result { diff --git a/src/handlers/update/pubkeys/ed25519.rs b/src/handlers/update/pubkeys/ed25519.rs index 1cf1aa7..df3b8a6 100644 --- a/src/handlers/update/pubkeys/ed25519.rs +++ b/src/handlers/update/pubkeys/ed25519.rs @@ -1,6 +1,6 @@ use ring::signature; -use crate::reader::Reader; +use crate::{handlers::update::sig::Algorithm, reader::Reader}; use super::{PublicKey, PublicKeyError, SSH_ED25519}; @@ -28,7 +28,12 @@ impl PublicKey for Ed25519PublicKey { Ok(Ed25519PublicKey { data: key.to_vec() }) } - fn verify(&self, data: &[u8], signature: &[u8]) -> Result { + fn verify( + &self, + data: &[u8], + signature: &[u8], + _algorithm: &Algorithm, + ) -> Result { let pkey = ring::signature::UnparsedPublicKey::new(&signature::ED25519, &self.data); Ok(pkey.verify(data, signature).is_ok()) diff --git a/src/handlers/update/pubkeys/mod.rs b/src/handlers/update/pubkeys/mod.rs index feedea5..1c238f4 100644 --- a/src/handlers/update/pubkeys/mod.rs +++ b/src/handlers/update/pubkeys/mod.rs @@ -3,13 +3,13 @@ mod rsa; use core::fmt; use std::str::from_utf8; -use base64::prelude::*; - use crate::{errors::ReaderError, reader::Reader}; pub use self::ed25519::Ed25519PublicKey; pub use self::rsa::RsaPublicKey; +use super::sig::Algorithm; + #[derive(Debug)] pub struct PublicKeyError { pub message: String, @@ -60,5 +60,10 @@ pub trait PublicKey { where Self: Sized; - fn verify(&self, data: &[u8], signature: &[u8]) -> Result; + fn verify( + &self, + data: &[u8], + signature: &[u8], + algorithm: &Algorithm, + ) -> Result; } diff --git a/src/handlers/update/pubkeys/rsa.rs b/src/handlers/update/pubkeys/rsa.rs index 02911e1..c228323 100644 --- a/src/handlers/update/pubkeys/rsa.rs +++ b/src/handlers/update/pubkeys/rsa.rs @@ -1,6 +1,6 @@ use ring::signature; -use crate::reader::Reader; +use crate::{handlers::update::sig::Algorithm, reader::Reader}; use super::{PublicKey, PublicKeyError, SSH_RSA}; @@ -29,7 +29,12 @@ impl PublicKey for RsaPublicKey { Ok(RsaPublicKey { e, n }) } - fn verify(&self, data: &[u8], signature: &[u8]) -> Result { + fn verify( + &self, + data: &[u8], + signature: &[u8], + algorithm: &Algorithm, + ) -> Result { let result = asn1::write_single(&RsaAsn1 { n: asn1::BigInt::new(&self.n), e: asn1::BigInt::new(&self.e), @@ -38,8 +43,15 @@ impl PublicKey for RsaPublicKey { message: format!("Verify Error: {}", e), })?; - let pkey = - ring::signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA512, result); + let signature_type = match algorithm { + 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()) } diff --git a/src/handlers/update/sig.rs b/src/handlers/update/sig.rs index 4c85005..5f67fb5 100644 --- a/src/handlers/update/sig.rs +++ b/src/handlers/update/sig.rs @@ -1,24 +1,85 @@ +use base64::prelude::*; +use int_enum::IntEnum; + use crate::{ errors::ParseError, parser::FromBytes, 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, - 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, +} + +/// 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 { + 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 { + 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 { - pub fn new(rr: &RR, datagram: &[u8]) -> Result { + pub fn new(rr: &RR, datagram: &[u8]) -> Result { let mut request = datagram[0..datagram.len() - 11 - rr.rdlength as usize].to_vec(); request[11] -= 1; // Decrease arcount 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(); raw_data.extend(request); @@ -29,8 +90,37 @@ impl Sig { }) } - pub fn verify(&self, key: impl PublicKey) -> bool { - key.verify(&self.raw_data, &self.key_rdata.signature) - .is_ok_and(|b| b) + fn verify(&self, key: impl PublicKey) -> Result { + key.verify( + &self.raw_data, + &self.key_rdata.signature, + &self.key_rdata.algo, + ) + } + + pub fn verify_ssh(&self, key: &str) -> Result { + 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 { + 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)?), + } + } } } diff --git a/src/parser.rs b/src/parser.rs index 6c8341e..226f92f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,9 +3,7 @@ use std::{mem::size_of, vec}; use crate::{ errors::ParseError, reader::Reader, - structs::{ - Class, Header, KeyRData, LabelString, Message, Opcode, Question, RRClass, RRType, Type, RR, - }, + structs::{Class, Header, LabelString, Message, Opcode, Question, RRClass, RRType, Type, RR}, }; type Result = std::result::Result; @@ -42,26 +40,18 @@ impl From for u16 { impl From for Type { fn from(value: u16) -> Self { - //TODO: use macro - match value { - 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::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), + match RRType::try_from(value) { + Ok(rrtype) => Type::Type(rrtype), + Err(x) => Type::Other(x), } } } impl From for Class { fn from(value: u16) -> Self { - match value { - x if x == RRClass::IN as u16 => Class::Class(RRClass::IN), - x if x == RRClass::ANY as u16 => Class::Class(RRClass::ANY), - x if x == RRClass::NONE as u16 => Class::Class(RRClass::NONE), - x => Class::Other(x), + match RRClass::try_from(value) { + Ok(rrclass) => Class::Class(rrclass), + Err(x) => Class::Other(x), } } } @@ -305,26 +295,3 @@ impl ToBytes for Message { result } } - -impl FromBytes for KeyRData { - fn from_bytes(reader: &mut Reader) -> Result { - 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())?, - }) - } - } -} diff --git a/src/structs.rs b/src/structs.rs index 58cf94b..f71ec0c 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,3 +1,5 @@ +use int_enum::IntEnum; + #[derive(Debug, Clone, PartialEq)] pub enum Type { Type(RRType), @@ -5,7 +7,7 @@ pub enum Type { } #[repr(u16)] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, IntEnum)] pub enum RRType { A = 1, SOA = 6, @@ -22,7 +24,7 @@ pub enum Class { } #[repr(u16)] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, IntEnum)] pub enum RRClass { IN = 1, NONE = 254, @@ -94,16 +96,3 @@ pub struct OptRR { } pub type LabelString = Vec; - -#[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, -}