From 9f4dc1ef09230f59b544eff55cbf2c31c68f241a Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Wed, 28 Aug 2024 00:05:37 +0800 Subject: [PATCH] feat: sshrw --- Cargo.toml | 2 + src/lib.rs | 1 + src/sshrw.rs | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 src/lib.rs create mode 100644 src/sshrw.rs diff --git a/Cargo.toml b/Cargo.toml index c1840ef..6843648 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,5 @@ license = "MIT" description = "OpenSSH Utils" [dependencies] +base64 = "0.22.1" +hex = "0.4.3" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..73338c2 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +mod sshrw; \ No newline at end of file diff --git a/src/sshrw.rs b/src/sshrw.rs new file mode 100644 index 0000000..a1917dd --- /dev/null +++ b/src/sshrw.rs @@ -0,0 +1,183 @@ +use base64::Engine; +use std::error::Error; +use std::io::{Cursor, Read}; + +const ED25519_PK_SZ: u32 = 32; + +pub type SshResult = Result>; + +pub struct SshReader { + buffer_len: usize, + read_len: usize, + buffer: Cursor>, +} + +impl SshReader { + pub fn new(bytes: Vec) -> Self { + Self { + buffer_len: bytes.len(), + read_len: 0, + buffer: Cursor::new(bytes), + } + } + + pub fn read_bytes(&mut self, len: u32) -> SshResult> { + let mut buff = vec![0_u8; len as usize]; + Cursor::read_exact(&mut self.buffer, &mut buff)?; + self.read_len += len as usize; + Ok(buff) + } + + pub fn read_u32(&mut self) -> SshResult { + let mut i = [0_u8; 4]; + i.copy_from_slice(&self.read_bytes(4)?); + Ok(u32::from_be_bytes(i)) + } + + pub fn read_string(&mut self) -> SshResult> { + let len = self.read_u32()?; + if len == 0 { + Ok(vec![]) + } else { + self.read_bytes(len) + } + } + + pub fn read_left(&mut self) -> SshResult> { + self.read_bytes(self.left_bytes() as u32) + } + + pub fn left_bytes(&self) -> usize { + println!("{} .. {}", self.read_len, self.buffer_len); + assert!(self.read_len <= self.buffer_len); + self.buffer_len - self.read_len + } +} + +pub struct SshWriter { + buffer: Vec, +} + +impl SshWriter { + pub fn new(bytes: Vec) -> Self { + Self { + buffer: bytes, + } + } + + pub fn write_bytes(&mut self, bytes: &[u8]) { + self.buffer.extend_from_slice(bytes); + } + + pub fn write_u32(&mut self, i: u32) { + self.write_bytes(&i.to_be_bytes()) + } + + pub fn write_string(&mut self, bytes: &[u8]) { + self.write_u32(bytes.len() as u32); + self.write_bytes(bytes); + } +} + +#[test] +fn test_rsa() { + use base64::engine::general_purpose::STANDARD; + let id_rsa_pub = "AAAAB3NzaC1yc2EAAAADAQABAAABgQC82oVSC64hu5H3bpdyZGNS9w7iJ/nF8iB6AYfpl\ +XdAMSZpDqHsGLfjj88nlJgb6mJCR0v1NhMNDRAcZ4VO/wLnLfZG384tgpbEuuBJsa21YYavcdPIWKnlhluNR\ +0NPGqMka6yFErJ97iPEaTJZdK0R1icOY5GNk71nKGDQP068l/GhH0CRwzZE2gAsfA3klndb1N0wSFaNUGrVb\ +aRfGb/KXQLJGJUP6Fn0JnAARNMojhYogJOYfF6jyqnfyn5NZxE9zL/ykCHZCKXZzfRKzNZMJ5NcDDhqkfoT7\ +aitryTP31BCQXynlkmG30gFL6f955H/hBMOVU1JgXR48dzGZwlEoJxeb35RGBzBruCESFkU4FzKjFcQxYbtg\ +XC/5zmN05vbpmZ3ZQO9CwR3ERDpkDNvMelxdxF529Ewi3ioHl+3fJVMjnZ18BUGkOn0bfVHIovX9+Jp6mhwo\ +k/QQmZ2Nu29OYSWITbWcoeszsC+AmpFa9zhF/TG+iwmRFKlmodyCWU="; + let mut ssh_reader = SshReader::new(STANDARD.decode(id_rsa_pub).unwrap()); + let algorithm = ssh_reader.read_string().unwrap(); + assert_eq!(b"ssh-rsa", algorithm.as_slice()); + + let id_rsa = "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"; +} + +#[test] +fn test_ecdsa() { + use base64::engine::general_purpose::STANDARD; + let id_ecdsa_pub = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLsD16oP\ +M9hkSeHxHrJ1+FMK2xNTd+LF17A/WW3YYTBCz8tQvhtgOQDiSk/9Lnc+vFlMF7LN0dwgwl4zIg07h0M="; + let mut ssh_reader = SshReader::new(STANDARD.decode(id_ecdsa_pub).unwrap()); + let algorithm = ssh_reader.read_string().unwrap(); + assert_eq!(b"ecdsa-sha2-nistp256", algorithm.as_slice()); + + let ecc_key_blob = ssh_reader.read_left().unwrap(); + let mut ecc_key_blob_reader = SshReader::new(ecc_key_blob); + let ssh_algorithm = ecc_key_blob_reader.read_string().unwrap(); + let ecc_key_point = ecc_key_blob_reader.read_string().unwrap(); + assert_eq!(b"nistp256", ssh_algorithm.as_slice()); + assert_eq!( + "04bb03d7aa0f33d86449e1f11eb275f8530adb135377e2c5d7b03f596dd8613042cfcb50be1b603900e24a4\ +ffd2e773ebc594c17b2cdd1dc20c25e33220d3b8743", + hex::encode(ecc_key_point)); + + assert_eq!(0, ssh_reader.left_bytes()); + + let id_ecdsa = "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\ +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS7A9eqDzPYZEnh8R6ydfhTCtsTU3fi\ +xdewP1lt2GEwQs/LUL4bYDkA4kpP/S53PrxZTBeyzdHcIMJeMyINO4dDAAAAuK3kG36t5B\" +t+AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLsD16oPM9hkSeHx\" +HrJ1+FMK2xNTd+LF17A/WW3YYTBCz8tQvhtgOQDiSk/9Lnc+vFlMF7LN0dwgwl4zIg07h0\" +MAAAAhAIC5cYUvZZw5X4LGn2hZR+l7kdiMKsTly/luHG7VNN4WAAAAHWhhdHRlcmppYW5n\" +QEhhdHRlckppYW5nX21hY09TAQI="; +} + +#[test] +fn test_ed25519() { + use base64::engine::general_purpose::STANDARD; + let id_ed25519_pub = "AAAAC3NzaC1lZDI1NTE5AAAAIEwcL5ke2mqRDXMWM34VW432M7fjASfvAUUTu7A7QqzW"; + let mut ssh_reader = SshReader::new(STANDARD.decode(id_ed25519_pub).unwrap()); + let algorithm = ssh_reader.read_string().unwrap(); + assert_eq!(b"ssh-ed25519", algorithm.as_slice()); + + let pub_key = ssh_reader.read_string().unwrap(); + assert_eq!("4c1c2f991eda6a910d7316337e155b8df633b7e30127ef014513bbb03b42acd6", + hex::encode(&pub_key)); + + assert_eq!(0, ssh_reader.left_bytes()); + + let id_ed25519 = "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\ +QyNTUxOQAAACBMHC+ZHtpqkQ1zFjN+FVuN9jO34wEn7wFFE7uwO0Ks1gAAAKCwNsNtsDbD\ +bQAAAAtzc2gtZWQyNTUxOQAAACBMHC+ZHtpqkQ1zFjN+FVuN9jO34wEn7wFFE7uwO0Ks1g\ +AAAEBG1hWO0trqejMtL8JlEUkm2w4+Wqvl4e4PkjC0Ee6fD0wcL5ke2mqRDXMWM34VW432\ +M7fjASfvAUUTu7A7QqzWAAAAHWhhdHRlcmppYW5nQEhhdHRlckppYW5nX21hY09T"; +} \ No newline at end of file