diff --git a/Cargo.lock b/Cargo.lock index 1b740cd..01de336 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,26 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "asn1" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532ceda058281b62096b2add4ab00ab3a453d30dee28b8890f62461a0109ebbd" +dependencies = [ + "asn1_derive", +] + +[[package]] +name = "asn1_derive" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6076d38cc17cc22b0f65f31170a2ee1975e6b07f0012893aefd86ce19c987" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -1296,6 +1316,7 @@ dependencies = [ name = "zeusns" version = "0.1.0" dependencies = [ + "asn1", "base64", "diesel", "dotenvy", diff --git a/Cargo.toml b/Cargo.toml index a1497dd..574c5be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ tokio = {version = "1.36.0", features = ["macros","rt-multi-thread","net"], defa ring = "0.17.8" base64 = "0.22.0" reqwest = {version = "0.12.4", features = ["json","default"]} +asn1 = "0.16.2" diff --git a/src/handlers/update/authenticate.rs b/src/handlers/update/authenticate.rs index 7b3cd5d..4faf112 100644 --- a/src/handlers/update/authenticate.rs +++ b/src/handlers/update/authenticate.rs @@ -1,4 +1,4 @@ -use reqwest::Error; +use base64::prelude::*; use crate::{ config::Config, @@ -9,7 +9,11 @@ use crate::{ structs::{Class, RRClass, RRType, Type}, }; -use super::{dnskey::DNSKeyRData, sig::Sig}; +use super::{ + dnskey::DNSKeyRData, + pubkeys::{Ed25519PublicKey, PublicKey, PublicKeyError, RsaPublicKey, SSH_ED25519, SSH_RSA}, + sig::Sig, +}; pub(super) async fn authenticate( sig: &Sig, @@ -32,7 +36,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, @@ -44,11 +48,13 @@ async fn validate_ssh(username: &String, sig: &Sig) -> Result { .iter() .any(|key| { let key_split: Vec<&str> = key.split_ascii_whitespace().collect(); - match key_split.len() { - 3 => match key_split[0] { - "ssh-ed25519" => sig.verify_ssh_ed25519(key_split[1]), - _ => false, - }, + 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, } })) @@ -67,3 +73,19 @@ 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 { + message: value.to_string(), + } + } +} diff --git a/src/handlers/update/dnskey.rs b/src/handlers/update/dnskey.rs index 01dc1ad..e58327a 100644 --- a/src/handlers/update/dnskey.rs +++ b/src/handlers/update/dnskey.rs @@ -1,8 +1,9 @@ -use base64::prelude::*; - use crate::{errors::ParseError, parser::FromBytes, reader::Reader}; -use super::sig::Sig; +use super::{ + pubkeys::{Ed25519PublicKey, PublicKey, RsaPublicKey}, + sig::Sig, +}; /// https://datatracker.ietf.org/doc/html/rfc4034#section-2 #[derive(Debug)] @@ -27,9 +28,12 @@ impl FromBytes for DNSKeyRData { impl DNSKeyRData { pub fn verify(&self, sig: &Sig) -> bool { - let encoded = BASE64_STANDARD.encode(&self.public_key); match self.algorithm { - 15 => sig.verify_ed25519(&encoded), + 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 b90f904..ede0c5b 100644 --- a/src/handlers/update/mod.rs +++ b/src/handlers/update/mod.rs @@ -11,6 +11,7 @@ use super::ResponseHandler; mod authenticate; mod dnskey; +mod pubkeys; mod sig; pub(super) struct UpdateHandler {} diff --git a/src/handlers/update/pubkeys/ed25519.rs b/src/handlers/update/pubkeys/ed25519.rs new file mode 100644 index 0000000..1cf1aa7 --- /dev/null +++ b/src/handlers/update/pubkeys/ed25519.rs @@ -0,0 +1,36 @@ +use ring::signature; + +use crate::reader::Reader; + +use super::{PublicKey, PublicKeyError, SSH_ED25519}; + +pub struct Ed25519PublicKey { + data: Vec, +} + +impl PublicKey for Ed25519PublicKey { + fn from_openssh(key: &[u8]) -> Result + where + Self: Sized, + { + let mut reader = Reader::new(key); + Ed25519PublicKey::verify_ssh_type(&mut reader, SSH_ED25519)?; + reader.read_i32()?; + Ok(Ed25519PublicKey { + data: reader.read(reader.unread_bytes())?, + }) + } + + fn from_dnskey(key: &[u8]) -> Result + where + Self: Sized, + { + Ok(Ed25519PublicKey { data: key.to_vec() }) + } + + fn verify(&self, data: &[u8], signature: &[u8]) -> 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 new file mode 100644 index 0000000..feedea5 --- /dev/null +++ b/src/handlers/update/pubkeys/mod.rs @@ -0,0 +1,64 @@ +mod ed25519; +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; + +#[derive(Debug)] +pub struct PublicKeyError { + pub message: String, +} + +impl fmt::Display for PublicKeyError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Public Key Error: {}", self.message) + } +} + +impl From for PublicKeyError { + fn from(value: ReaderError) -> Self { + PublicKeyError { + message: value.to_string(), + } + } +} + +pub const SSH_ED25519: &str = "ssh-ed25519"; +pub const SSH_RSA: &str = "ssh-rsa"; + +pub trait PublicKey { + fn verify_ssh_type(reader: &mut Reader, key_type: &str) -> Result<(), PublicKeyError> { + let type_size = reader.read_i32()?; + let read = reader.read(type_size as usize)?; + let algo_type = from_utf8(&read).map_err(|e| PublicKeyError { + message: format!( + "Could not convert type name bytes to string: {}", + e.to_string() + ), + })?; + + if algo_type == key_type { + Ok(()) + } else { + Err(PublicKeyError { + message: String::from("ssh key type does not match identifier"), + }) + } + } + + fn from_openssh(key: &[u8]) -> Result + where + Self: Sized; + + fn from_dnskey(key: &[u8]) -> Result + where + Self: Sized; + + fn verify(&self, data: &[u8], signature: &[u8]) -> Result; +} diff --git a/src/handlers/update/pubkeys/rsa.rs b/src/handlers/update/pubkeys/rsa.rs new file mode 100644 index 0000000..d765511 --- /dev/null +++ b/src/handlers/update/pubkeys/rsa.rs @@ -0,0 +1,57 @@ +use ring::signature; + +use crate::reader::Reader; + +use super::{PublicKey, PublicKeyError, SSH_RSA}; + +pub struct RsaPublicKey { + e: Vec, + n: Vec, +} + +#[derive(asn1::Asn1Write)] +struct RsaAsn1<'a> { + n: Option>, + e: Option>, +} + +impl PublicKey for RsaPublicKey { + fn from_openssh(key: &[u8]) -> Result + where + Self: Sized, + { + let mut reader = Reader::new(key); + RsaPublicKey::verify_ssh_type(&mut reader, SSH_RSA)?; + let e_size = reader.read_i32()?; + let e = reader.read(e_size as usize)?; + let n_size = reader.read_i32()?; + let n = reader.read(n_size as usize)?; + Ok(RsaPublicKey { e, n }) + } + + fn verify(&self, data: &[u8], signature: &[u8]) -> Result { + let result = asn1::write_single(&RsaAsn1 { + n: asn1::BigInt::new(&self.n), + e: asn1::BigInt::new(&self.e), + }) + .map_err(|e| PublicKeyError { + message: format!("Verify Error: {}", e), + })?; + + let pkey = + ring::signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA512, result); + + Ok(pkey.verify(data, signature).is_ok()) + } + + fn from_dnskey(key: &[u8]) -> Result + where + Self: Sized, + { + let mut reader = Reader::new(key); + let e_len = reader.read_u8()?; + let e = reader.read(e_len as usize)?; + let n = reader.read(reader.unread_bytes())?; + Ok(RsaPublicKey { e, n }) + } +} diff --git a/src/handlers/update/sig.rs b/src/handlers/update/sig.rs index ae0ddca..4c85005 100644 --- a/src/handlers/update/sig.rs +++ b/src/handlers/update/sig.rs @@ -1,5 +1,3 @@ -use base64::prelude::*; - use crate::{ errors::ParseError, parser::FromBytes, @@ -7,6 +5,8 @@ use crate::{ structs::{KeyRData, RR}, }; +use super::pubkeys::PublicKey; + pub(super) struct Sig { raw_data: Vec, key_rdata: KeyRData, @@ -29,24 +29,8 @@ impl Sig { }) } - pub fn verify_ed25519(&self, key: &str) -> bool { - let blob = BASE64_STANDARD.decode(key).unwrap(); - - let pkey = ring::signature::UnparsedPublicKey::new(&ring::signature::ED25519, &blob); - - pkey.verify(&self.raw_data, &self.key_rdata.signature) - .is_ok() - } - - pub fn verify_ssh_ed25519(&self, key: &str) -> 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: impl PublicKey) -> bool { + key.verify(&self.raw_data, &self.key_rdata.signature) + .is_ok_and(|b| b) } }