Rdp nla completed
This commit is contained in:
parent
121eeb74ef
commit
6bb3b637ea
@ -22,7 +22,7 @@ cd ${SSH} && cargo make install-release && cd ..
|
||||
[tasks.websockify]
|
||||
dependencies = ["install-dir"]
|
||||
script = '''
|
||||
cd ${WEBSOCKIFY} && cargo build --release && cp ./target/release/${WEBSOCKIFY} $INSTALL_PATH/
|
||||
cd ${WEBSOCKIFY} && cargo build --release --features ssl && cp ./target/release/${WEBSOCKIFY} $INSTALL_PATH/
|
||||
'''
|
||||
|
||||
[tasks.install-dir]
|
||||
@ -36,4 +36,4 @@ INSTALL_PATH= "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/build"
|
||||
WEBSOCKIFY="axum-websockify"
|
||||
VNC="webvnc"
|
||||
RDP="webrdp"
|
||||
SSH="webssh"
|
||||
SSH="webssh"
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit ed5e2b2f2415f4b31159ea23a1e7f1018dd7275f
|
||||
Subproject commit 516a59b315b20161522982b9efe5919a14b59649
|
@ -12,6 +12,12 @@ default = ["console_error_panic_hook"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2.63"
|
||||
js-sys = "0.3"
|
||||
untrusted = "0.9"
|
||||
md4 = "0.10.2"
|
||||
hmac = "0.12.1"
|
||||
md-5 = "0.10.5"
|
||||
x509-parser = "0.14.0"
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
@ -19,6 +25,27 @@ wasm-bindgen = "0.2.63"
|
||||
# code size when deploying.
|
||||
console_error_panic_hook = { version = "0.1.6", optional = true }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.22"
|
||||
features = [
|
||||
"BinaryType",
|
||||
"Blob",
|
||||
"CanvasRenderingContext2d",
|
||||
"Document",
|
||||
"ErrorEvent",
|
||||
"FileReader",
|
||||
"HtmlButtonElement",
|
||||
"HtmlCanvasElement",
|
||||
"ImageData",
|
||||
"Location",
|
||||
"KeyboardEvent",
|
||||
"MouseEvent",
|
||||
"MessageEvent",
|
||||
"ProgressEvent",
|
||||
"Window",
|
||||
"WebSocket",
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.13"
|
||||
|
||||
|
@ -32,9 +32,9 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="vnc_status" style="position: relative; height: auto;" class="horizontal-centre vertical-centre"></div>
|
||||
<div id="rdp_status" style="position: relative; height: auto;" class="horizontal-centre vertical-centre"></div>
|
||||
<div id="canvas" class="horizontal-centre vertical-centre">
|
||||
<canvas id="vnc-canvas" tabIndex=1></canvas>
|
||||
<canvas id="rdp-canvas" tabIndex=1></canvas>
|
||||
<button type="button" id="ctrlaltdel" style="display: inline; position:absolute; right: 10px; top: 10px;">Send
|
||||
CtrlAltDel</button>
|
||||
</div>
|
||||
|
251
webrdp/src/canvas.rs
Normal file
251
webrdp/src/canvas.rs
Normal file
@ -0,0 +1,251 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
console_log, log,
|
||||
rdp::{ImageData, ImageType, MouseEventType, Rdp},
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::{Clamped, JsCast};
|
||||
use web_sys::{
|
||||
CanvasRenderingContext2d, HtmlButtonElement, HtmlCanvasElement, KeyboardEvent, MouseEvent,
|
||||
};
|
||||
struct Canvas {
|
||||
canvas: HtmlCanvasElement,
|
||||
ctx: CanvasRenderingContext2d,
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
fn new() -> Self {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
let canvas = document.get_element_by_id("rdp-canvas").unwrap();
|
||||
let canvas: HtmlCanvasElement = canvas
|
||||
.dyn_into::<HtmlCanvasElement>()
|
||||
.map_err(|_| ())
|
||||
.unwrap();
|
||||
let ctx = canvas
|
||||
.get_context("2d")
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.dyn_into::<CanvasRenderingContext2d>()
|
||||
.unwrap();
|
||||
Self { canvas, ctx }
|
||||
}
|
||||
|
||||
fn set_resolution(&self, width: u32, height: u32) {
|
||||
// set hight & width
|
||||
self.canvas.set_height(height);
|
||||
self.canvas.set_width(width);
|
||||
self.ctx.rect(0_f64, 0_f64, width as f64, height as f64);
|
||||
self.ctx.fill();
|
||||
}
|
||||
|
||||
fn bind(&self, rdp: &Rdp) {
|
||||
let handler = rdp.clone();
|
||||
let key_down = move |e: KeyboardEvent| {
|
||||
e.prevent_default();
|
||||
e.stop_propagation();
|
||||
handler.key_press(e, true);
|
||||
};
|
||||
|
||||
let handler = Box::new(key_down) as Box<dyn FnMut(_)>;
|
||||
|
||||
let cb = Closure::wrap(handler);
|
||||
|
||||
self.canvas
|
||||
.add_event_listener_with_callback("keydown", cb.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
cb.forget();
|
||||
|
||||
let handler = rdp.clone();
|
||||
let key_up = move |e: KeyboardEvent| {
|
||||
e.prevent_default();
|
||||
e.stop_propagation();
|
||||
handler.key_press(e, false);
|
||||
};
|
||||
|
||||
let handler = Box::new(key_up) as Box<dyn FnMut(_)>;
|
||||
|
||||
let cb = Closure::wrap(handler);
|
||||
|
||||
self.canvas
|
||||
.add_event_listener_with_callback("keyup", cb.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
cb.forget();
|
||||
|
||||
let handler = rdp.clone();
|
||||
let ctrl_alt_del_btn = web_sys::window()
|
||||
.unwrap()
|
||||
.document()
|
||||
.unwrap()
|
||||
.get_element_by_id("ctrlaltdel")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlButtonElement>()
|
||||
.map_err(|_| ())
|
||||
.unwrap();
|
||||
let ctrl_alt_del = move || {
|
||||
handler.ctrl_alt_del();
|
||||
};
|
||||
let handler = Box::new(ctrl_alt_del) as Box<dyn FnMut()>;
|
||||
|
||||
let cb = Closure::wrap(handler);
|
||||
|
||||
ctrl_alt_del_btn.set_onclick(Some(cb.as_ref().unchecked_ref()));
|
||||
cb.forget();
|
||||
|
||||
// On a conventional mouse, buttons 1, 2, and 3 correspond to the left,
|
||||
// middle, and right buttons on the mouse. On a wheel mouse, each step
|
||||
// of the wheel upwards is represented by a press and release of button
|
||||
// 4, and each step downwards is represented by a press and release of
|
||||
// button 5.
|
||||
|
||||
// to do:
|
||||
// calculate relation position
|
||||
let handler = rdp.clone();
|
||||
let mouse_move = move |e: MouseEvent| {
|
||||
e.stop_propagation();
|
||||
handler.mouse_event(e, MouseEventType::Move);
|
||||
};
|
||||
|
||||
let handler = Box::new(mouse_move) as Box<dyn FnMut(_)>;
|
||||
|
||||
let cb = Closure::wrap(handler);
|
||||
|
||||
self.canvas
|
||||
.add_event_listener_with_callback("mousemove", cb.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
cb.forget();
|
||||
|
||||
let handler = rdp.clone();
|
||||
let mouse_down = move |e: MouseEvent| {
|
||||
e.stop_propagation();
|
||||
handler.mouse_event(e, MouseEventType::Down);
|
||||
};
|
||||
|
||||
let handler = Box::new(mouse_down) as Box<dyn FnMut(_)>;
|
||||
|
||||
let cb = Closure::wrap(handler);
|
||||
|
||||
self.canvas
|
||||
.add_event_listener_with_callback("mousedown", cb.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
cb.forget();
|
||||
|
||||
let handler = rdp.clone();
|
||||
let mouse_up = move |e: MouseEvent| {
|
||||
e.stop_propagation();
|
||||
handler.mouse_event(e, MouseEventType::Up);
|
||||
};
|
||||
|
||||
let handler = Box::new(mouse_up) as Box<dyn FnMut(_)>;
|
||||
|
||||
let cb = Closure::wrap(handler);
|
||||
|
||||
self.canvas
|
||||
.add_event_listener_with_callback("mouseup", cb.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
cb.forget();
|
||||
|
||||
let get_context_menu = move |e: MouseEvent| {
|
||||
e.prevent_default();
|
||||
e.stop_propagation();
|
||||
};
|
||||
|
||||
let handler = Box::new(get_context_menu) as Box<dyn FnMut(_)>;
|
||||
|
||||
let cb = Closure::wrap(handler);
|
||||
|
||||
self.canvas
|
||||
.add_event_listener_with_callback("contextmenu", cb.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
cb.forget();
|
||||
}
|
||||
|
||||
fn draw(&self, ri: &ImageData) {
|
||||
match ri.type_ {
|
||||
ImageType::Copy => {
|
||||
//copy
|
||||
let sx = (ri.data[0] as u16) << 8 | ri.data[1] as u16;
|
||||
let sy = (ri.data[2] as u16) << 8 | ri.data[3] as u16;
|
||||
|
||||
let _ = self
|
||||
.ctx
|
||||
.draw_image_with_html_canvas_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
|
||||
&self.canvas,
|
||||
sx as f64,
|
||||
sy as f64,
|
||||
ri.width as f64,
|
||||
ri.height as f64,
|
||||
ri.x as f64,
|
||||
ri.y as f64,
|
||||
ri.width as f64,
|
||||
ri.height as f64,
|
||||
);
|
||||
}
|
||||
ImageType::Fill => {
|
||||
// fill
|
||||
let (r, g, b) = (ri.data[2], ri.data[1], ri.data[0]);
|
||||
let style = format!("rgb({},{},{})", r, g, b);
|
||||
self.ctx.set_fill_style(&JsValue::from_str(&style));
|
||||
}
|
||||
ImageType::Raw => {
|
||||
let data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(
|
||||
Clamped(&ri.data),
|
||||
ri.width as u32,
|
||||
ri.height as u32,
|
||||
);
|
||||
if data.is_err() {
|
||||
console_log!(
|
||||
"renderring failed at ({}, {}), width {}, height {}, len {}",
|
||||
ri.x,
|
||||
ri.y,
|
||||
ri.width,
|
||||
ri.height,
|
||||
ri.data.len(),
|
||||
);
|
||||
}
|
||||
let data = data.unwrap();
|
||||
let _ = self.ctx.put_image_data(&data, ri.x as f64, ri.y as f64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&self) {
|
||||
self.ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CanvasUtils {
|
||||
inner: Rc<Canvas>,
|
||||
}
|
||||
|
||||
impl Clone for CanvasUtils {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CanvasUtils {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Rc::new(Canvas::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&self, width: u32, height: u32) {
|
||||
self.inner.as_ref().set_resolution(width, height);
|
||||
}
|
||||
|
||||
pub fn bind(&self, rdp: &Rdp) {
|
||||
self.inner.as_ref().bind(rdp);
|
||||
}
|
||||
|
||||
pub fn draw(&self, ri: &ImageData) {
|
||||
self.inner.as_ref().draw(ri);
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
self.inner.as_ref().close()
|
||||
}
|
||||
}
|
@ -1,19 +1,187 @@
|
||||
mod canvas;
|
||||
mod rdp;
|
||||
mod utils;
|
||||
|
||||
use canvas::CanvasUtils;
|
||||
use rdp::Rdp;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{ErrorEvent, HtmlButtonElement, MessageEvent, WebSocket};
|
||||
|
||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||
// allocator.
|
||||
#[cfg(feature = "wee_alloc")]
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
#[macro_export]
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
fn alert(s: &str);
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn log(s: &str);
|
||||
pub fn setClipBoard(s: String);
|
||||
pub fn getClipBoard() -> String;
|
||||
}
|
||||
|
||||
fn rdp_close_handle(rdp: &Rdp, canvas: &CanvasUtils, msg: &str) {
|
||||
rdp.close();
|
||||
// unsafe {
|
||||
// REFRESHER.take();
|
||||
// }
|
||||
canvas.close();
|
||||
let status = web_sys::window()
|
||||
.unwrap()
|
||||
.document()
|
||||
.unwrap()
|
||||
.get_element_by_id("rdp_status")
|
||||
.unwrap();
|
||||
status.set_text_content(Some(msg));
|
||||
}
|
||||
|
||||
fn rdp_out_handler(ws: &WebSocket, rdp: &Rdp, canvas: &CanvasUtils) {
|
||||
let out = rdp.get_output();
|
||||
if let Some(out) = out {
|
||||
for ref o in out {
|
||||
match o {
|
||||
rdp::RdpOutput::Err(err) => {
|
||||
console_log!("Err {}", err);
|
||||
rdp_close_handle(rdp, canvas, err);
|
||||
}
|
||||
rdp::RdpOutput::WsBuf(buf) => match ws.send_with_u8_array(buf) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
let err = format!("error sending message: {:?}", err);
|
||||
rdp_close_handle(rdp, canvas, &err);
|
||||
}
|
||||
},
|
||||
rdp::RdpOutput::RequireSSL => match ws.send_with_str("SSL") {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
let err = format!("error launching ssl: {:?}", err);
|
||||
rdp_close_handle(rdp, canvas, &err);
|
||||
}
|
||||
},
|
||||
rdp::RdpOutput::RequirePassword => {
|
||||
// let pwd = prompt("Please input the password");
|
||||
// rdp.set_credential(&pwd);
|
||||
// rdp_out_handler(ws, rdp, canvas);
|
||||
}
|
||||
rdp::RdpOutput::RenderImage(ri) => {
|
||||
canvas.draw(ri);
|
||||
}
|
||||
rdp::RdpOutput::SetResolution(x, y) => {
|
||||
canvas.init(*x as u32, *y as u32);
|
||||
canvas.bind(rdp);
|
||||
// rdp.require_frame(0);
|
||||
rdp_out_handler(ws, rdp, canvas);
|
||||
|
||||
// let vnc_cloned = rdp.clone();
|
||||
// let ws_cloned = ws.clone();
|
||||
// let canvas_cloned = canvas.clone();
|
||||
|
||||
// set a interval for fps enhance
|
||||
// let refresh = move || {
|
||||
// vnc_cloned.require_frame(1);
|
||||
// rdp_out_handler(&ws_cloned, &vnc_cloned, &canvas_cloned);
|
||||
// };
|
||||
|
||||
// let refersher = Interval::new(20, refresh);
|
||||
|
||||
// unsafe {
|
||||
// REFRESHER = Some(refersher);
|
||||
// }
|
||||
}
|
||||
rdp::RdpOutput::SetClipboard(text) => {
|
||||
setClipBoard(text.to_owned());
|
||||
// ConsoleService::log(&self.error_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_websocket() -> Result<(), JsValue> {
|
||||
// connect
|
||||
let url = format!(
|
||||
"{scheme}://{host}/websockify",
|
||||
scheme = if web_sys::window()
|
||||
.unwrap()
|
||||
.location()
|
||||
.protocol()?
|
||||
.starts_with("https")
|
||||
{
|
||||
"wss"
|
||||
} else {
|
||||
"ws"
|
||||
},
|
||||
host = web_sys::window().unwrap().location().host()?
|
||||
);
|
||||
let ws = WebSocket::new_with_str(&url, "binary")?;
|
||||
let canvas = CanvasUtils::new();
|
||||
let rdp = Rdp::new();
|
||||
|
||||
let clipboard = web_sys::window()
|
||||
.unwrap()
|
||||
.document()
|
||||
.unwrap()
|
||||
.get_element_by_id("clipboardsend")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlButtonElement>()
|
||||
.map_err(|_| ())
|
||||
.unwrap();
|
||||
let rdp_cloned = rdp.clone();
|
||||
let onclickcb = Closure::<dyn FnMut()>::new(move || {
|
||||
console_log!("Send {:?}", getClipBoard());
|
||||
rdp_cloned.set_clipboard(&getClipBoard());
|
||||
});
|
||||
clipboard.set_onclick(Some(onclickcb.as_ref().unchecked_ref()));
|
||||
onclickcb.forget();
|
||||
|
||||
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
|
||||
// on message
|
||||
let cloned_ws = ws.clone();
|
||||
let rdp_cloned = rdp.clone();
|
||||
let canvas_cloned = canvas.clone();
|
||||
|
||||
let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
|
||||
if let Ok(abuf) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
|
||||
let array = js_sys::Uint8Array::new(&abuf);
|
||||
// let mut canvas_ctx = None;
|
||||
rdp_cloned.do_input(array.to_vec());
|
||||
rdp_out_handler(&cloned_ws, &rdp_cloned, &canvas_cloned);
|
||||
} else {
|
||||
console_log!("message event, received Unknown: {:?}", e.data());
|
||||
}
|
||||
});
|
||||
ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
|
||||
// forget the callback to keep it alive
|
||||
onmessage_callback.forget();
|
||||
|
||||
// onerror
|
||||
let onerror_callback = Closure::<dyn FnMut(_)>::new(move |e: ErrorEvent| {
|
||||
console_log!("error event: {:?}", e);
|
||||
});
|
||||
ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
|
||||
onerror_callback.forget();
|
||||
|
||||
// onopen
|
||||
let rdp_cloned = rdp.clone();
|
||||
let ws_cloned = ws.clone();
|
||||
let canvas_cloned = canvas.clone();
|
||||
let onopen_callback = Closure::<dyn FnMut()>::new(move || {
|
||||
console_log!("socket opened");
|
||||
rdp_cloned.init();
|
||||
rdp_out_handler(&ws_cloned, &rdp_cloned, &canvas_cloned);
|
||||
});
|
||||
ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
|
||||
onopen_callback.forget();
|
||||
|
||||
let onclose_callback = Closure::<dyn FnMut()>::new(move || {
|
||||
console_log!("socket close");
|
||||
rdp_close_handle(&rdp, &canvas, "Disconnected");
|
||||
});
|
||||
ws.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
|
||||
onclose_callback.forget();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
259
webrdp/src/rdp/mod.rs
Normal file
259
webrdp/src/rdp/mod.rs
Normal file
@ -0,0 +1,259 @@
|
||||
mod protocol;
|
||||
mod rdp_impl;
|
||||
mod x11cursor;
|
||||
mod x11keyboard;
|
||||
|
||||
use rdp_impl::RdpInner;
|
||||
use std::{rc::Rc, sync::Mutex};
|
||||
|
||||
type ConnectCb = fn(&mut RdpInner);
|
||||
type FailCb = fn(&mut RdpInner, &str);
|
||||
pub trait Engine {
|
||||
fn hello(&mut self, rdp: &mut RdpInner);
|
||||
fn do_input(&mut self, rdp: &mut RdpInner);
|
||||
}
|
||||
|
||||
pub enum MouseEventType {
|
||||
Down,
|
||||
Up,
|
||||
Move,
|
||||
}
|
||||
|
||||
pub struct ImageData {
|
||||
pub type_: ImageType,
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub enum RdpOutput {
|
||||
WsBuf(Vec<u8>),
|
||||
Err(String),
|
||||
RequireSSL,
|
||||
RequirePassword,
|
||||
SetResolution(u16, u16),
|
||||
RenderImage(ImageData),
|
||||
SetClipboard(String),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum ImageType {
|
||||
Raw,
|
||||
Copy,
|
||||
Fill,
|
||||
}
|
||||
|
||||
pub struct Rdp {
|
||||
inner: Rc<Mutex<RdpInner>>,
|
||||
}
|
||||
|
||||
impl Rdp {
|
||||
pub fn new() -> Self {
|
||||
Rdp {
|
||||
inner: Rc::new(Mutex::new(RdpInner::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&self) {
|
||||
self.inner.lock().unwrap().init();
|
||||
}
|
||||
|
||||
pub fn key_press(&self, _key: web_sys::KeyboardEvent, _down: bool) {
|
||||
// self.inner.lock().unwrap().key_press(key, down);
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn mouse_event(&self, _mouse: web_sys::MouseEvent, _et: MouseEventType) {
|
||||
// self.inner.lock().unwrap().mouse_event(mouse, et);
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn ctrl_alt_del(&self) {
|
||||
// self.inner.lock().unwrap().ctrl_alt_del();
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn set_clipboard(&self, _s: &str) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn do_input(&self, data: Vec<u8>) {
|
||||
self.inner.lock().unwrap().do_input(data);
|
||||
}
|
||||
|
||||
pub fn get_output(&self) -> Option<Vec<RdpOutput>> {
|
||||
self.inner.lock().unwrap().get_output()
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
self.inner.lock().unwrap().close()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Rdp {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StreamReader {
|
||||
inner: Vec<Vec<u8>>,
|
||||
remain: usize,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl StreamReader {
|
||||
pub fn new(bufs: Vec<Vec<u8>>, len: usize) -> Self {
|
||||
Self {
|
||||
inner: bufs,
|
||||
remain: len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(&mut self, buf: Vec<u8>) {
|
||||
self.remain += buf.len();
|
||||
self.inner.push(buf);
|
||||
}
|
||||
|
||||
pub fn remain(&self) -> usize {
|
||||
self.remain
|
||||
}
|
||||
|
||||
pub fn read_exact_vec(&mut self, out_vec: &mut Vec<u8>, n: usize) {
|
||||
let mut i = 0;
|
||||
let mut left = n;
|
||||
self.remain -= n;
|
||||
while i < n {
|
||||
if self.inner[0].len() > left {
|
||||
for it in self.inner[0].drain(0..left) {
|
||||
out_vec.push(it);
|
||||
}
|
||||
i += left;
|
||||
left = 0;
|
||||
} else {
|
||||
out_vec.extend(&self.inner[0]);
|
||||
left -= self.inner[0].len();
|
||||
i += self.inner[0].len();
|
||||
self.inner.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_exact(&mut self, out_buf: &mut [u8], n: usize) {
|
||||
let mut i = 0;
|
||||
let mut left = n;
|
||||
self.remain -= n;
|
||||
while i < n {
|
||||
if self.inner[0].len() > left {
|
||||
out_buf[i..i + left].copy_from_slice(&self.inner[0][0..left]);
|
||||
self.inner[0].drain(0..left);
|
||||
i += left;
|
||||
left = 0;
|
||||
} else {
|
||||
out_buf[i..i + self.inner[0].len()].copy_from_slice(&self.inner[0]);
|
||||
left -= self.inner[0].len();
|
||||
i += self.inner[0].len();
|
||||
self.inner.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_u8(&mut self) -> u8 {
|
||||
let mut buf = [0u8; 1];
|
||||
self.read_exact(&mut buf, 1);
|
||||
buf[0]
|
||||
}
|
||||
|
||||
pub fn read_u16_be(&mut self) -> u16 {
|
||||
let mut buf = [0u8; 2];
|
||||
self.read_exact(&mut buf, 2);
|
||||
u16::from_be_bytes(buf)
|
||||
}
|
||||
|
||||
pub fn read_u32_be(&mut self) -> u32 {
|
||||
let mut buf = [0u8; 4];
|
||||
self.read_exact(&mut buf, 4);
|
||||
u32::from_be_bytes(buf)
|
||||
}
|
||||
|
||||
pub fn read_u64_be(&mut self) -> u64 {
|
||||
let mut buf = [0u8; 8];
|
||||
self.read_exact(&mut buf, 8);
|
||||
u64::from_be_bytes(buf)
|
||||
}
|
||||
|
||||
pub fn read_u16_le(&mut self) -> u16 {
|
||||
let mut buf = [0u8; 2];
|
||||
self.read_exact(&mut buf, 2);
|
||||
u16::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
pub fn read_u32_le(&mut self) -> u32 {
|
||||
let mut buf = [0u8; 4];
|
||||
self.read_exact(&mut buf, 4);
|
||||
u32::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
pub fn read_u64_le(&mut self) -> u64 {
|
||||
let mut buf = [0u8; 8];
|
||||
self.read_exact(&mut buf, 8);
|
||||
u64::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
pub fn read_string(&mut self, len: usize) -> String {
|
||||
let mut buf = vec![0u8; len];
|
||||
self.read_exact(&mut buf, len);
|
||||
String::from_utf8(buf).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StreamWriter {
|
||||
buf: Vec<u8>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl StreamWriter {
|
||||
pub fn new(buf: Vec<u8>) -> Self {
|
||||
Self { buf }
|
||||
}
|
||||
|
||||
pub fn write_u8(&mut self, b: u8) {
|
||||
self.buf.push(b);
|
||||
}
|
||||
|
||||
pub fn write_u16_be(&mut self, b: u16) {
|
||||
self.buf.extend_from_slice(&b.to_be_bytes());
|
||||
}
|
||||
|
||||
pub fn write_u32_be(&mut self, b: u32) {
|
||||
self.buf.extend_from_slice(&b.to_be_bytes());
|
||||
}
|
||||
|
||||
pub fn write_u16_le(&mut self, b: u16) {
|
||||
self.buf.extend_from_slice(&b.to_le_bytes());
|
||||
}
|
||||
|
||||
pub fn write_u32_le(&mut self, b: u32) {
|
||||
self.buf.extend_from_slice(&b.to_le_bytes());
|
||||
}
|
||||
|
||||
pub fn write_string(&mut self, s: &str) {
|
||||
self.buf.extend_from_slice(s.as_bytes());
|
||||
}
|
||||
|
||||
pub fn write_slice(&mut self, s: &[u8]) {
|
||||
self.buf.extend_from_slice(s);
|
||||
}
|
||||
|
||||
pub fn get_inner(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> Vec<u8> {
|
||||
self.buf
|
||||
}
|
||||
}
|
308
webrdp/src/rdp/protocol/ber.rs
Normal file
308
webrdp/src/rdp/protocol/ber.rs
Normal file
@ -0,0 +1,308 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use crate::rdp::StreamWriter;
|
||||
|
||||
/* Class - bits 8 and 7 */
|
||||
const BER_CLASS_UNIV: u8 = 0x00;
|
||||
const BER_CLASS_MASK: u8 = 0xC0;
|
||||
const BER_CLASS_APPL: u8 = 0x40;
|
||||
const BER_CLASS_CTXT: u8 = 0x80;
|
||||
const BER_CLASS_PRIV: u8 = 0xc0;
|
||||
/* P/C - bit 6 */
|
||||
const BER_PC_MASK: u8 = 0x20;
|
||||
const BER_PRIMITIVE: u8 = 0x00; /* 0 */
|
||||
const BER_CONSTRUCT: u8 = 0x20; /* 1 */
|
||||
/* Tag - bits 5 to 1 */
|
||||
const BER_TAG_MASK: u8 = 0x1F;
|
||||
const BER_TAG_BOOLEAN: u8 = 0x01;
|
||||
const BER_TAG_INTEGER: u8 = 0x02;
|
||||
const BER_TAG_BIT_STRING: u8 = 0x03;
|
||||
const BER_TAG_OCTET_STRING: u8 = 0x04;
|
||||
const BER_TAG_OBJECT_IDENFIER: u8 = 0x06;
|
||||
const BER_TAG_ENUMERATED: u8 = 0x0A;
|
||||
const BER_TAG_SEQUENCE: u8 = 0x10;
|
||||
const BER_TAG_SEQUENCE_OF: u8 = 0x10;
|
||||
|
||||
/// Enum all possible value
|
||||
/// In an ASN 1 tree
|
||||
#[derive(Debug)]
|
||||
pub enum ASN1Type<'a> {
|
||||
/// A list of ASN1 node equivalent to component
|
||||
// Sequence(Cow<'a, &'a [BerObj<'a>]>),
|
||||
Sequence(&'a [BerObj<'a>]),
|
||||
SequenceOwned(Vec<BerObj<'a>>),
|
||||
/// Unsigned 32 bits type
|
||||
U32(u32),
|
||||
/// Octet string
|
||||
OctetString(&'a [u8]),
|
||||
/// Boolean
|
||||
Bool(bool),
|
||||
/// Enumerate
|
||||
Enumerate(i64),
|
||||
// Private Type
|
||||
Priv(&'a BerObj<'a>),
|
||||
PrivOwned(Box<BerObj<'a>>),
|
||||
}
|
||||
|
||||
impl<'a> From<u32> for ASN1Type<'a> {
|
||||
fn from(v: u32) -> Self {
|
||||
Self::U32(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for ASN1Type<'a> {
|
||||
fn from(b: &'a [u8]) -> Self {
|
||||
Self::OctetString(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [BerObj<'a>]> for ASN1Type<'a> {
|
||||
fn from(seq: &'a [BerObj<'a>]) -> Self {
|
||||
Self::Sequence(seq)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Vec<BerObj<'a>>> for ASN1Type<'a> {
|
||||
fn from(seq: Vec<BerObj<'a>>) -> Self {
|
||||
Self::SequenceOwned(seq)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<BerObj<'a>> for ASN1Type<'a> {
|
||||
fn from(ber: BerObj<'a>) -> Self {
|
||||
Self::PrivOwned(Box::new(ber))
|
||||
}
|
||||
}
|
||||
|
||||
fn BER_PC(pc: bool) -> u8 {
|
||||
if pc {
|
||||
BER_CONSTRUCT
|
||||
} else {
|
||||
BER_PRIMITIVE
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BerObj<'a> {
|
||||
value: ASN1Type<'a>,
|
||||
tag: u8,
|
||||
}
|
||||
|
||||
// fn make_outlive<'a>(anchor: &'a [u8], value: Vec<BerObj<'a>>) -> &'a [BerObj<'a>] {
|
||||
// value.as_slice()
|
||||
// }
|
||||
|
||||
impl<'a> BerObj<'a> {
|
||||
pub fn new_with_tag(tag: u8, value: &'a BerObj<'a>) -> Self {
|
||||
Self {
|
||||
value: ASN1Type::Priv(value),
|
||||
tag: (BER_CLASS_CTXT | BER_PC(true)) | (BER_TAG_MASK & tag),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(value: ASN1Type<'a>) -> Self {
|
||||
fn universal_tag(ty: u8, pc: bool) -> u8 {
|
||||
(BER_CLASS_UNIV | BER_PC(pc)) | (BER_TAG_MASK & ty)
|
||||
}
|
||||
let tag = match value {
|
||||
ASN1Type::U32(_) => universal_tag(BER_TAG_INTEGER, false),
|
||||
ASN1Type::Sequence(_) => universal_tag(BER_TAG_SEQUENCE, true),
|
||||
ASN1Type::SequenceOwned(_) => universal_tag(BER_TAG_SEQUENCE, true),
|
||||
ASN1Type::OctetString(_) => universal_tag(BER_TAG_OCTET_STRING, false),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Self { value, tag }
|
||||
}
|
||||
|
||||
pub fn from_der(der: &'a [u8]) -> Self {
|
||||
let mut cursor = 0;
|
||||
let tag = der[cursor];
|
||||
let pc = tag & BER_CONSTRUCT;
|
||||
let ctx = tag & BER_CLASS_CTXT;
|
||||
let tag_masked = tag & BER_TAG_MASK;
|
||||
cursor += 1;
|
||||
|
||||
let mut len = der[cursor] as u16;
|
||||
cursor += 1;
|
||||
if len == 0x82 {
|
||||
let mut blen = [0; 2];
|
||||
blen[0] = der[cursor];
|
||||
cursor += 1;
|
||||
blen[1] = der[cursor];
|
||||
cursor += 1;
|
||||
len = u16::from_be_bytes(blen);
|
||||
} else if len == 0x81 {
|
||||
len = der[cursor] as u16;
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
match (ctx, pc, tag_masked) {
|
||||
(BER_CLASS_UNIV, BER_CONSTRUCT, BER_TAG_SEQUENCE) => {
|
||||
let mut seq = Vec::new();
|
||||
while cursor < der.len() {
|
||||
let subobj = BerObj::from_der(&der[cursor..]);
|
||||
let sublen = subobj.total_length();
|
||||
seq.push(subobj);
|
||||
cursor += sublen as usize;
|
||||
}
|
||||
BerObj {
|
||||
tag,
|
||||
value: seq.into(),
|
||||
}
|
||||
}
|
||||
(BER_CLASS_UNIV, BER_PRIMITIVE, BER_TAG_INTEGER) => {
|
||||
let mut value = 0;
|
||||
for i in 0..len {
|
||||
value <<= 8;
|
||||
value |= der[cursor] as u32;
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
BerObj {
|
||||
tag,
|
||||
value: value.into(),
|
||||
}
|
||||
}
|
||||
(BER_CLASS_UNIV, BER_PRIMITIVE, BER_TAG_OCTET_STRING) => BerObj {
|
||||
tag,
|
||||
value: (&der[cursor..cursor + len as usize]).into(),
|
||||
},
|
||||
(BER_CLASS_CTXT, BER_CONSTRUCT, _) => BerObj {
|
||||
tag,
|
||||
value: BerObj::from_der(&der[cursor..]).into(),
|
||||
},
|
||||
// (BER_CLASS_CTXT, BER_CONSTRUCT, tag) => {}
|
||||
(x, y, z) => unreachable!("ctx: {}, pc {}, tag {}", x, y, z),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_der(&self) -> Vec<u8> {
|
||||
let len = self.value_len();
|
||||
let out = Vec::new();
|
||||
let mut sw = StreamWriter::new(out);
|
||||
|
||||
// tag
|
||||
sw.write_u8(self.tag);
|
||||
|
||||
// length
|
||||
if len > 0xff {
|
||||
sw.write_u8(0x80 ^ 2);
|
||||
sw.write_u16_be(len);
|
||||
} else if len > 0x7f {
|
||||
sw.write_u8(0x80 ^ 1);
|
||||
sw.write_u8(len.try_into().unwrap());
|
||||
} else {
|
||||
sw.write_u8(len.try_into().unwrap());
|
||||
}
|
||||
|
||||
// value
|
||||
match self.value {
|
||||
ASN1Type::U32(x) => {
|
||||
if x < 0x80 {
|
||||
sw.write_u8(x.try_into().unwrap());
|
||||
} else if x < 0x8000 {
|
||||
sw.write_u16_be(x.try_into().unwrap());
|
||||
} else if x < 0x800000 {
|
||||
sw.write_u8((x >> 16).try_into().unwrap());
|
||||
sw.write_u16_be((x & 0xffff).try_into().unwrap())
|
||||
} else if x < 0x80000000 {
|
||||
sw.write_u32_be(x);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
ASN1Type::OctetString(s) => {
|
||||
sw.write_slice(s);
|
||||
}
|
||||
ASN1Type::Priv(p) => sw.write_slice(&p.to_der()),
|
||||
ASN1Type::PrivOwned(ref p) => sw.write_slice(&p.to_der()),
|
||||
ASN1Type::Sequence(seq) => {
|
||||
for ber in seq {
|
||||
sw.write_slice(&ber.to_der())
|
||||
}
|
||||
}
|
||||
ASN1Type::SequenceOwned(ref seq) => {
|
||||
for ber in seq {
|
||||
sw.write_slice(&ber.to_der())
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
sw.into_inner()
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> &ASN1Type {
|
||||
&self.value
|
||||
}
|
||||
|
||||
fn total_length(&self) -> u16 {
|
||||
let value_len = self.value_len();
|
||||
// tag (1 byte) - length (1-3 byte) - value (value_len)
|
||||
if value_len > 0xff {
|
||||
value_len + 4
|
||||
} else if value_len > 0x7f {
|
||||
value_len + 3
|
||||
} else {
|
||||
value_len + 2
|
||||
}
|
||||
}
|
||||
|
||||
fn value_len(&self) -> u16 {
|
||||
match self.value {
|
||||
ASN1Type::U32(x) => {
|
||||
if x < 0x80 {
|
||||
1
|
||||
} else if x < 0x8000 {
|
||||
2
|
||||
} else if x < 0x800000 {
|
||||
3
|
||||
} else if x < 0x80000000 {
|
||||
4
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
ASN1Type::OctetString(s) => s.len().try_into().unwrap(),
|
||||
ASN1Type::Priv(p) => p.total_length(),
|
||||
ASN1Type::PrivOwned(ref p) => p.total_length(),
|
||||
ASN1Type::Sequence(seq) => {
|
||||
let mut len = 0;
|
||||
for ber in seq {
|
||||
len += ber.total_length();
|
||||
}
|
||||
len
|
||||
}
|
||||
ASN1Type::SequenceOwned(ref seq) => {
|
||||
let mut len = 0;
|
||||
for ber in seq {
|
||||
len += ber.total_length();
|
||||
}
|
||||
len
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ber {
|
||||
($tag: expr, $val:expr) => {
|
||||
BerObj::new_with_tag($tag, $val)
|
||||
};
|
||||
|
||||
($val: expr) => {
|
||||
BerObj::new($val.into())
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! ber_seq {
|
||||
($($bers:expr),*) => {
|
||||
BerObj::new(([$($bers), *][..]).into())
|
||||
};
|
||||
|
||||
($($bers:expr,)*) => {
|
||||
ber_seq!($($bers), *)
|
||||
};
|
||||
}
|
4
webrdp/src/rdp/protocol/mod.rs
Normal file
4
webrdp/src/rdp/protocol/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
#[macro_use]
|
||||
mod ber;
|
||||
pub(super) mod nla;
|
||||
pub(super) mod x224;
|
195
webrdp/src/rdp/protocol/nla/cssp.rs
Normal file
195
webrdp/src/rdp/protocol/nla/cssp.rs
Normal file
@ -0,0 +1,195 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use super::ntlm::*;
|
||||
use super::{super::ber::*, to_unicode};
|
||||
use crate::rdp::rdp_impl::RdpInner;
|
||||
use crate::rdp::*;
|
||||
use x509_parser::prelude::*;
|
||||
|
||||
enum State {
|
||||
Init,
|
||||
ServerPubkeyWait,
|
||||
ClientNegoSent,
|
||||
ClientChalSent,
|
||||
ServerAuthRecv,
|
||||
}
|
||||
|
||||
pub struct CsspClient {
|
||||
on_connect: ConnectCb,
|
||||
on_fail: FailCb,
|
||||
server_key: Vec<u8>,
|
||||
state: State,
|
||||
ntlm: Ntlm,
|
||||
username: String,
|
||||
password: String,
|
||||
domain: String,
|
||||
hostname: String,
|
||||
send_seq: u32,
|
||||
recv_seq: u32,
|
||||
}
|
||||
|
||||
impl CsspClient {
|
||||
pub fn new(on_connect: ConnectCb, on_fail: FailCb, u: &str, p: &str, d: &str, h: &str) -> Self {
|
||||
Self {
|
||||
on_connect,
|
||||
on_fail,
|
||||
server_key: Vec::new(),
|
||||
state: State::Init,
|
||||
ntlm: Ntlm::new(u, p, d, h),
|
||||
username: u.to_string(),
|
||||
password: p.to_string(),
|
||||
domain: d.to_string(),
|
||||
hostname: h.to_string(),
|
||||
send_seq: 0,
|
||||
recv_seq: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Engine for CsspClient {
|
||||
fn hello(&mut self, rdp: &mut RdpInner) {
|
||||
self.state = State::ServerPubkeyWait;
|
||||
self.ntlm
|
||||
.init(ISC_REQ_MUTUAL_AUTH | ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY);
|
||||
rdp.wait(1);
|
||||
}
|
||||
fn do_input(&mut self, rdp: &mut RdpInner) {
|
||||
match self.state {
|
||||
State::Init => unreachable!(),
|
||||
State::ServerPubkeyWait => self.handle_server_publickey(rdp),
|
||||
State::ClientNegoSent => self.handle_server_challenge(rdp),
|
||||
State::ClientChalSent => self.handle_server_auth(rdp),
|
||||
State::ServerAuthRecv => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CsspClient {
|
||||
fn handle_server_publickey(&mut self, rdp: &mut RdpInner) {
|
||||
let mut x509_der = Vec::new();
|
||||
rdp.reader
|
||||
.read_exact_vec(&mut x509_der, rdp.reader.remain());
|
||||
let x509 = X509Certificate::from_der(&x509_der).unwrap();
|
||||
self.server_key = x509
|
||||
.1
|
||||
.tbs_certificate
|
||||
.subject_pki
|
||||
.subject_public_key
|
||||
.data
|
||||
.to_vec();
|
||||
self.ntlm.generate_nego();
|
||||
|
||||
self.send_tsrequest(rdp, self.ntlm.get_nego_msg());
|
||||
self.state = State::ClientNegoSent;
|
||||
rdp.wait(3); // any valid ans.1 struct
|
||||
}
|
||||
|
||||
fn handle_server_challenge(&mut self, rdp: &mut RdpInner) {
|
||||
let mut server_challenge = Vec::with_capacity(rdp.reader.remain());
|
||||
rdp.reader
|
||||
.read_exact_vec(&mut server_challenge, rdp.reader.remain());
|
||||
let ans1_tree = BerObj::from_der(&server_challenge);
|
||||
// console_log!("ans1_tree {:#?}", ans1_tree);
|
||||
if let ASN1Type::SequenceOwned(ref seq) = ans1_tree.get_value() {
|
||||
if let ASN1Type::PrivOwned(nego_tokens) = seq[1].get_value() {
|
||||
if let ASN1Type::SequenceOwned(ref nego_seq) = nego_tokens.get_value() {
|
||||
if let ASN1Type::SequenceOwned(ref nego_items) = nego_seq[0].get_value() {
|
||||
if let ASN1Type::PrivOwned(nego_item) = nego_items[0].get_value() {
|
||||
if let ASN1Type::OctetString(server_chal) = nego_item.get_value() {
|
||||
self.ntlm.generate_client_chal(server_chal);
|
||||
} else {
|
||||
panic!("Unknown response");
|
||||
}
|
||||
} else {
|
||||
panic!("Unknown response");
|
||||
}
|
||||
} else {
|
||||
panic!("Unknown response");
|
||||
}
|
||||
} else {
|
||||
panic!("Unknown response");
|
||||
}
|
||||
} else {
|
||||
panic!("Unknown response");
|
||||
}
|
||||
} else {
|
||||
panic!("Unknown response");
|
||||
}
|
||||
let pubkey_auth = self.ntlm.encrypt(&self.server_key, self.send_seq);
|
||||
self.send_seq += 1;
|
||||
self.send_ts_challenge(rdp, self.ntlm.get_chal_msg(), &pubkey_auth);
|
||||
self.state = State::ClientChalSent;
|
||||
rdp.wait(3); // any valid ans.1 struct
|
||||
}
|
||||
|
||||
fn handle_server_auth(&mut self, rdp: &mut RdpInner) {
|
||||
let mut not_in_use = Vec::new();
|
||||
rdp.reader
|
||||
.read_exact_vec(&mut not_in_use, rdp.reader.remain());
|
||||
|
||||
let auth_data = ber_seq!(
|
||||
/* [0] credType (INTEGER) */
|
||||
ber!(0, &ber!(1)),
|
||||
/* [1] credentials (OCTET STRING) */
|
||||
ber!(
|
||||
1,
|
||||
&ber!(
|
||||
/* make the whole credentials as an octet string */
|
||||
&ber_seq!(
|
||||
ber!(0, &ber!(to_unicode(&self.domain)[..])),
|
||||
ber!(1, &ber!(to_unicode(&self.username)[..])),
|
||||
ber!(2, &ber!(to_unicode(&self.password)[..]))
|
||||
)
|
||||
.to_der()[..]
|
||||
)
|
||||
)
|
||||
)
|
||||
.to_der();
|
||||
let auth = self.ntlm.encrypt(&auth_data, self.send_seq);
|
||||
self.send_seq += 1;
|
||||
self.send_ts_auth(rdp, &auth);
|
||||
}
|
||||
|
||||
fn send_tsrequest(&self, rdp: &mut RdpInner, nego: &[u8]) {
|
||||
let output = ber_seq!(
|
||||
/* [0] version, 2 */
|
||||
ber!(0, &ber!(2)),
|
||||
/* [1] negoTokens(NegoData) */
|
||||
ber!(
|
||||
1,
|
||||
/* SEQUENCE OF NegoDataItem */
|
||||
&ber_seq!(/* NegoDataItem */ ber_seq!(ber!(0, &ber!(nego))))
|
||||
)
|
||||
)
|
||||
.to_der();
|
||||
rdp.writer.write_slice(&output);
|
||||
}
|
||||
|
||||
fn send_ts_challenge(&self, rdp: &mut RdpInner, nego: &[u8], pubkey: &[u8]) {
|
||||
let output = ber_seq!(
|
||||
/* [0] version, 2 */
|
||||
ber!(0, &ber!(2)),
|
||||
/* [1] negoTokens(NegoData) */
|
||||
ber!(
|
||||
1,
|
||||
/* SEQUENCE OF NegoDataItem */
|
||||
&ber_seq!(/* NegoDataItem */ ber_seq!(ber!(0, &ber!(nego))))
|
||||
),
|
||||
/* [3] pubKeyAuth (OCTET STRING) */
|
||||
ber!(3, &ber!(pubkey))
|
||||
)
|
||||
.to_der();
|
||||
rdp.writer.write_slice(&output);
|
||||
}
|
||||
|
||||
fn send_ts_auth(&self, rdp: &mut RdpInner, auth: &[u8]) {
|
||||
let output = ber_seq!(
|
||||
/* [0] version, 2 */
|
||||
ber!(0, &ber!(2)),
|
||||
/* [2] authInfo (OCTET STRING) */
|
||||
ber!(2, &ber!(auth))
|
||||
)
|
||||
.to_der();
|
||||
rdp.writer.write_slice(&output);
|
||||
}
|
||||
}
|
24
webrdp/src/rdp/protocol/nla/mod.rs
Normal file
24
webrdp/src/rdp/protocol/nla/mod.rs
Normal file
@ -0,0 +1,24 @@
|
||||
mod cssp;
|
||||
mod ntlm;
|
||||
mod rc4;
|
||||
|
||||
pub use cssp::CsspClient as Nla;
|
||||
|
||||
fn from_unicode(buf: &[u8]) -> String {
|
||||
String::from_utf16_lossy(
|
||||
buf.chunks_exact(2)
|
||||
.into_iter()
|
||||
.map(|a| u16::from_le_bytes([a[0], a[1]]))
|
||||
.collect::<Vec<u16>>()
|
||||
.as_slice(),
|
||||
)
|
||||
}
|
||||
|
||||
fn to_unicode(s: &str) -> Vec<u8> {
|
||||
s.encode_utf16()
|
||||
.collect::<Vec<u16>>()
|
||||
.into_iter()
|
||||
.map(u16::to_le_bytes)
|
||||
.flat_map(IntoIterator::into_iter)
|
||||
.collect::<Vec<u8>>()
|
||||
}
|
982
webrdp/src/rdp/protocol/nla/ntlm.rs
Normal file
982
webrdp/src/rdp/protocol/nla/ntlm.rs
Normal file
@ -0,0 +1,982 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use super::rc4::Rc4;
|
||||
use super::*;
|
||||
use crate::rdp::StreamWriter;
|
||||
use hmac::{Hmac, Mac};
|
||||
#[cfg(not(test))]
|
||||
use js_sys::Math::random;
|
||||
use md4::{Digest, Md4};
|
||||
use md5::Md5;
|
||||
use std::collections::HashMap;
|
||||
use untrusted::{Input, Reader};
|
||||
|
||||
const NTLMSSP_NEGOTIATE_56: u32 = 0x80000000; /* W (0) */
|
||||
const NTLMSSP_NEGOTIATE_KEY_EXCH: u32 = 0x40000000; /* V (1) */
|
||||
const NTLMSSP_NEGOTIATE_128: u32 = 0x20000000; /* U (2) */
|
||||
const NTLMSSP_RESERVED1: u32 = 0x10000000; /* r1 (3) */
|
||||
const NTLMSSP_RESERVED2: u32 = 0x08000000; /* r2 (4) */
|
||||
const NTLMSSP_RESERVED3: u32 = 0x04000000; /* r3 (5) */
|
||||
const NTLMSSP_NEGOTIATE_VERSION: u32 = 0x02000000; /* T (6) */
|
||||
const NTLMSSP_RESERVED4: u32 = 0x01000000; /* r4 (7) */
|
||||
const NTLMSSP_NEGOTIATE_TARGET_INFO: u32 = 0x00800000; /* S (8) */
|
||||
const NTLMSSP_REQUEST_NON_NT_SESSION_KEY: u32 = 0x00400000; /* R (9) */
|
||||
const NTLMSSP_RESERVED5: u32 = 0x00200000; /* r5 (10) */
|
||||
const NTLMSSP_NEGOTIATE_IDENTIFY: u32 = 0x00100000; /* Q (11) */
|
||||
const NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY: u32 = 0x00080000; /* P (12) */
|
||||
const NTLMSSP_RESERVED6: u32 = 0x00040000; /* r6 (13) */
|
||||
const NTLMSSP_TARGET_TYPE_SERVER: u32 = 0x00020000; /* O (14) */
|
||||
const NTLMSSP_TARGET_TYPE_DOMAIN: u32 = 0x00010000; /* N (15) */
|
||||
const NTLMSSP_NEGOTIATE_ALWAYS_SIGN: u32 = 0x00008000; /* M (16) */
|
||||
const NTLMSSP_RESERVED7: u32 = 0x00004000; /* r7 (17) */
|
||||
const NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED: u32 = 0x00002000; /* L (18) */
|
||||
const NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED: u32 = 0x00001000; /* K (19) */
|
||||
const NTLMSSP_NEGOTIATE_ANONYMOUS: u32 = 0x00000800; /* J (20) */
|
||||
const NTLMSSP_RESERVED8: u32 = 0x00000400; /* r8 (21) */
|
||||
const NTLMSSP_NEGOTIATE_NTLM: u32 = 0x00000200; /* H (22) */
|
||||
const NTLMSSP_RESERVED9: u32 = 0x00000100; /* r9 (23) */
|
||||
const NTLMSSP_NEGOTIATE_LM_KEY: u32 = 0x00000080; /* G (24) */
|
||||
const NTLMSSP_NEGOTIATE_DATAGRAM: u32 = 0x00000040; /* F (25) */
|
||||
const NTLMSSP_NEGOTIATE_SEAL: u32 = 0x00000020; /* E (26) */
|
||||
const NTLMSSP_NEGOTIATE_SIGN: u32 = 0x00000010; /* D (27) */
|
||||
const NTLMSSP_RESERVED10: u32 = 0x00000008; /* r10 (28) */
|
||||
const NTLMSSP_REQUEST_TARGET: u32 = 0x00000004; /* C (29) */
|
||||
const NTLMSSP_NEGOTIATE_OEM: u32 = 0x00000002; /* B (30) */
|
||||
const NTLMSSP_NEGOTIATE_UNICODE: u32 = 0x00000001; /* A (31) */
|
||||
|
||||
pub const ISC_REQ_DELEGATE: u32 = 0x00000001;
|
||||
pub const ISC_REQ_MUTUAL_AUTH: u32 = 0x00000002;
|
||||
pub const ISC_REQ_REPLAY_DETECT: u32 = 0x00000004;
|
||||
pub const ISC_REQ_SEQUENCE_DETECT: u32 = 0x00000008;
|
||||
pub const ISC_REQ_CONFIDENTIALITY: u32 = 0x00000010;
|
||||
pub const ISC_REQ_USE_SESSION_KEY: u32 = 0x00000020;
|
||||
pub const ISC_REQ_PROMPT_FOR_CREDS: u32 = 0x00000040;
|
||||
pub const ISC_REQ_USE_SUPPLIED_CREDS: u32 = 0x00000080;
|
||||
pub const ISC_REQ_ALLOCATE_MEMORY: u32 = 0x00000100;
|
||||
pub const ISC_REQ_USE_DCE_STYLE: u32 = 0x00000200;
|
||||
pub const ISC_REQ_DATAGRAM: u32 = 0x00000400;
|
||||
pub const ISC_REQ_CONNECTION: u32 = 0x00000800;
|
||||
pub const ISC_REQ_CALL_LEVEL: u32 = 0x00001000;
|
||||
pub const ISC_REQ_FRAGMENT_SUPPLIED: u32 = 0x00002000;
|
||||
pub const ISC_REQ_EXTENDED_ERROR: u32 = 0x00004000;
|
||||
pub const ISC_REQ_STREAM: u32 = 0x00008000;
|
||||
pub const ISC_REQ_INTEGRITY: u32 = 0x00010000;
|
||||
pub const ISC_REQ_IDENTIFY: u32 = 0x00020000;
|
||||
pub const ISC_REQ_NULL_SESSION: u32 = 0x00040000;
|
||||
pub const ISC_REQ_MANUAL_CRED_VALIDATION: u32 = 0x00080000;
|
||||
pub const ISC_REQ_RESERVED1: u32 = 0x00100000;
|
||||
pub const ISC_REQ_FRAGMENT_TO_FIT: u32 = 0x00200000;
|
||||
pub const ISC_REQ_FORWARD_CREDENTIALS: u32 = 0x00400000;
|
||||
pub const ISC_REQ_NO_INTEGRITY: u32 = 0x00800000;
|
||||
pub const ISC_REQ_USE_HTTP_STYLE: u32 = 0x01000000;
|
||||
|
||||
pub const ISC_RET_DELEGATE: u32 = 0x00000001;
|
||||
pub const ISC_RET_MUTUAL_AUTH: u32 = 0x00000002;
|
||||
pub const ISC_RET_REPLAY_DETECT: u32 = 0x00000004;
|
||||
pub const ISC_RET_SEQUENCE_DETECT: u32 = 0x00000008;
|
||||
pub const ISC_RET_CONFIDENTIALITY: u32 = 0x00000010;
|
||||
pub const ISC_RET_USE_SESSION_KEY: u32 = 0x00000020;
|
||||
pub const ISC_RET_USED_COLLECTED_CREDS: u32 = 0x00000040;
|
||||
pub const ISC_RET_USED_SUPPLIED_CREDS: u32 = 0x00000080;
|
||||
pub const ISC_RET_ALLOCATED_MEMORY: u32 = 0x00000100;
|
||||
pub const ISC_RET_USED_DCE_STYLE: u32 = 0x00000200;
|
||||
pub const ISC_RET_DATAGRAM: u32 = 0x00000400;
|
||||
pub const ISC_RET_CONNECTION: u32 = 0x00000800;
|
||||
pub const ISC_RET_INTERMEDIATE_RETURN: u32 = 0x00001000;
|
||||
pub const ISC_RET_CALL_LEVEL: u32 = 0x00002000;
|
||||
pub const ISC_RET_EXTENDED_ERROR: u32 = 0x00004000;
|
||||
pub const ISC_RET_STREAM: u32 = 0x00008000;
|
||||
pub const ISC_RET_INTEGRITY: u32 = 0x00010000;
|
||||
pub const ISC_RET_IDENTIFY: u32 = 0x00020000;
|
||||
pub const ISC_RET_NULL_SESSION: u32 = 0x00040000;
|
||||
pub const ISC_RET_MANUAL_CRED_VALIDATION: u32 = 0x00080000;
|
||||
pub const ISC_RET_RESERVED1: u32 = 0x00100000;
|
||||
pub const ISC_RET_FRAGMENT_ONLY: u32 = 0x00200000;
|
||||
pub const ISC_RET_FORWARD_CREDENTIALS: u32 = 0x00400000;
|
||||
pub const ISC_RET_USED_HTTP_STYLE: u32 = 0x01000000;
|
||||
|
||||
const MsvAvEOL: u16 = 0x0;
|
||||
const MsvAvNbComputerName: u16 = 0x1;
|
||||
const MsvAvNbDomainName: u16 = 0x2;
|
||||
const MsvAvDnsComputerName: u16 = 0x3;
|
||||
const MsvAvDnsDomainName: u16 = 0x4;
|
||||
const MsvAvDnsTreeName: u16 = 0x5;
|
||||
const MsvAvFlags: u16 = 0x6;
|
||||
const MsvAvTimestamp: u16 = 0x7;
|
||||
const MsvAvSingleHost: u16 = 0x8;
|
||||
const MsvAvTargetName: u16 = 0x9;
|
||||
const MsvChannelBindings: u16 = 0xa;
|
||||
|
||||
const MSV_AV_FLAGS_AUTHENTICATION_CONSTRAINED: u32 = 0x00000001;
|
||||
const MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK: u32 = 0x00000002;
|
||||
const MSV_AV_FLAGS_TARGET_SPN_UNTRUSTED_SOURCE: u32 = 0x00000004;
|
||||
|
||||
const NTML_MAGIC: &str = "NTLMSSP\0";
|
||||
|
||||
const CLIENT_SIGN_MAGIC: &[u8] = b"session key to client-to-server signing key magic constant\0";
|
||||
const SERVER_SIGN_MAGIC: &[u8] = b"session key to server-to-client signing key magic constant\0";
|
||||
const CLIENT_SEAL_MAGIC: &[u8] = b"session key to client-to-server sealing key magic constant\0";
|
||||
const SERVER_SEAL_MAGIC: &[u8] = b"session key to server-to-client sealing key magic constant\0";
|
||||
|
||||
fn read_u8(reader: &mut Reader) -> u8 {
|
||||
reader.read_byte().unwrap()
|
||||
}
|
||||
|
||||
fn read_u16(reader: &mut Reader) -> u16 {
|
||||
u16::from_le_bytes(
|
||||
reader
|
||||
.read_bytes(2)
|
||||
.unwrap()
|
||||
.as_slice_less_safe()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn read_u32(reader: &mut Reader) -> u32 {
|
||||
u32::from_le_bytes(
|
||||
reader
|
||||
.read_bytes(4)
|
||||
.unwrap()
|
||||
.as_slice_less_safe()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn read_utf8(reader: &mut Reader, len: usize) -> String {
|
||||
String::from_utf8_lossy(reader.read_bytes(len).unwrap().as_slice_less_safe()).to_string()
|
||||
}
|
||||
|
||||
fn read_utf16(reader: &mut Reader, len: usize) -> String {
|
||||
from_unicode(reader.read_bytes(len).unwrap().as_slice_less_safe())
|
||||
}
|
||||
|
||||
fn read_exact_vec(reader: &mut Reader, len: usize) -> Vec<u8> {
|
||||
reader
|
||||
.read_bytes(len)
|
||||
.unwrap()
|
||||
.as_slice_less_safe()
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
fn hmac_md5(key: &[u8], msg: &[u8]) -> Vec<u8> {
|
||||
let mut stream = Hmac::<Md5>::new_from_slice(key).unwrap();
|
||||
stream.update(msg);
|
||||
stream.finalize().into_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn rc4k(key: &[u8], plaintext: &[u8]) -> Vec<u8> {
|
||||
let mut result = vec![0; plaintext.len()];
|
||||
let mut rc4_handle = Rc4::new(key);
|
||||
rc4_handle.process(plaintext, &mut result);
|
||||
result
|
||||
}
|
||||
|
||||
struct NtlmMsgObj<'a> {
|
||||
len: u16,
|
||||
maxlen: u16,
|
||||
offset: u32,
|
||||
content: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> NtlmMsgObj<'a> {
|
||||
fn new(reader: &mut Reader, buf: &'a [u8]) -> Self {
|
||||
let len = read_u16(reader);
|
||||
let maxlen = read_u16(reader);
|
||||
let offset = read_u32(reader);
|
||||
Self {
|
||||
len,
|
||||
maxlen,
|
||||
offset,
|
||||
content: &buf[offset as usize..offset as usize + len as usize],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NtlmServerInfo {
|
||||
server_name: String,
|
||||
server_chal: [u8; 8],
|
||||
server_nego: u32,
|
||||
server_computer_name: Option<String>,
|
||||
server_domain_name: Option<String>,
|
||||
server_dns_computer_name: Option<String>,
|
||||
server_dns_tree_name: Option<String>,
|
||||
server_dns_domain_name: Option<String>,
|
||||
server_flags: Option<u32>,
|
||||
server_timestamp: Option<Vec<u8>>,
|
||||
server_single_host: Option<Vec<u8>>,
|
||||
server_target_name: Option<String>,
|
||||
server_server_channel_binds: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl NtlmServerInfo {
|
||||
fn new_from(
|
||||
target_name: &NtlmMsgObj,
|
||||
nego_flag: u32,
|
||||
server_challenge: &[u8],
|
||||
target_info: &NtlmMsgObj,
|
||||
) -> Self {
|
||||
let server_name = from_unicode(target_name.content);
|
||||
let server_chal = server_challenge;
|
||||
let server_nego = nego_flag;
|
||||
let mut reader = Reader::new(Input::from(target_info.content));
|
||||
let mut new_obj = Self {
|
||||
server_name,
|
||||
server_chal: server_chal.try_into().unwrap(),
|
||||
server_nego,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
loop {
|
||||
let av_id = read_u16(&mut reader);
|
||||
let av_len = read_u16(&mut reader);
|
||||
|
||||
match av_id {
|
||||
MsvAvEOL => break,
|
||||
MsvAvNbComputerName => {
|
||||
new_obj.server_computer_name = Some(read_utf16(&mut reader, av_len as usize))
|
||||
}
|
||||
MsvAvNbDomainName => {
|
||||
new_obj.server_domain_name = Some(read_utf16(&mut reader, av_len as usize))
|
||||
}
|
||||
MsvAvDnsComputerName => {
|
||||
new_obj.server_dns_computer_name =
|
||||
Some(read_utf16(&mut reader, av_len as usize))
|
||||
}
|
||||
MsvAvDnsDomainName => {
|
||||
new_obj.server_dns_domain_name = Some(read_utf16(&mut reader, av_len as usize))
|
||||
}
|
||||
MsvAvDnsTreeName => {
|
||||
new_obj.server_dns_tree_name = Some(read_utf16(&mut reader, av_len as usize))
|
||||
}
|
||||
MsvAvFlags => new_obj.server_flags = Some(read_u32(&mut reader)),
|
||||
MsvAvTimestamp => {
|
||||
new_obj.server_timestamp = Some(read_exact_vec(&mut reader, av_len as usize))
|
||||
}
|
||||
MsvAvSingleHost => {
|
||||
new_obj.server_single_host = Some(read_exact_vec(&mut reader, av_len as usize))
|
||||
}
|
||||
MsvAvTargetName => {
|
||||
new_obj.server_target_name = Some(read_utf16(&mut reader, av_len as usize))
|
||||
}
|
||||
MsvChannelBindings => {
|
||||
new_obj.server_server_channel_binds =
|
||||
Some(read_exact_vec(&mut reader, av_len as usize))
|
||||
}
|
||||
_ => panic!("Unknown av"),
|
||||
}
|
||||
}
|
||||
new_obj
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Ntlm {
|
||||
nego_msg: Vec<u8>,
|
||||
chal_msg: Vec<u8>,
|
||||
ntlm_v2: bool,
|
||||
confidentiality: bool,
|
||||
use_mic: bool,
|
||||
send_version_info: bool,
|
||||
send_single_host_data: bool,
|
||||
send_workstation_name: bool,
|
||||
suppress_extended_protection: bool,
|
||||
username: String,
|
||||
password: String,
|
||||
domain: String,
|
||||
hostname: String,
|
||||
server_info: NtlmServerInfo,
|
||||
auth_info: HashMap<&'static str, Vec<u8>>,
|
||||
encrypt: Option<Rc4>,
|
||||
decrypt: Option<Rc4>,
|
||||
}
|
||||
|
||||
impl Ntlm {
|
||||
pub fn new(u: &str, p: &str, d: &str, h: &str) -> Self {
|
||||
Self {
|
||||
ntlm_v2: true,
|
||||
send_version_info: true,
|
||||
send_workstation_name: true,
|
||||
use_mic: true,
|
||||
username: u.to_string(),
|
||||
password: p.to_string(),
|
||||
domain: d.to_string(),
|
||||
hostname: h.to_string(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn init(&mut self, flag: u32) {
|
||||
if flag & ISC_REQ_CONFIDENTIALITY > 0 {
|
||||
self.confidentiality = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_nego(&mut self) {
|
||||
let nego_msg = Vec::new();
|
||||
let mut flags = 0;
|
||||
let mut sw = StreamWriter::new(nego_msg);
|
||||
|
||||
/* signature */
|
||||
sw.write_string(NTML_MAGIC);
|
||||
/* type */
|
||||
sw.write_u32_le(1);
|
||||
/* flags */
|
||||
if self.ntlm_v2 {
|
||||
flags |= NTLMSSP_NEGOTIATE_56;
|
||||
flags |= NTLMSSP_NEGOTIATE_VERSION;
|
||||
flags |= NTLMSSP_NEGOTIATE_LM_KEY;
|
||||
flags |= NTLMSSP_NEGOTIATE_OEM;
|
||||
}
|
||||
flags |= NTLMSSP_NEGOTIATE_KEY_EXCH;
|
||||
flags |= NTLMSSP_NEGOTIATE_128;
|
||||
flags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY;
|
||||
flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
||||
flags |= NTLMSSP_NEGOTIATE_NTLM;
|
||||
flags |= NTLMSSP_NEGOTIATE_SIGN;
|
||||
flags |= NTLMSSP_REQUEST_TARGET;
|
||||
flags |= NTLMSSP_NEGOTIATE_UNICODE;
|
||||
|
||||
if self.confidentiality {
|
||||
flags |= NTLMSSP_NEGOTIATE_SEAL;
|
||||
}
|
||||
|
||||
if self.send_version_info {
|
||||
flags |= NTLMSSP_NEGOTIATE_VERSION;
|
||||
}
|
||||
sw.write_u32_le(flags);
|
||||
|
||||
/* domain */
|
||||
sw.write_u16_le(0);
|
||||
sw.write_u16_le(0);
|
||||
sw.write_u32_le(40);
|
||||
|
||||
/* workstation */
|
||||
sw.write_u16_le(0);
|
||||
sw.write_u16_le(0);
|
||||
sw.write_u32_le(40);
|
||||
|
||||
/* version */
|
||||
sw.write_u8(6); // dwMajorVersion
|
||||
sw.write_u8(1); // dwMinorVersion
|
||||
sw.write_u16_le(7601); // dwBuildNumber
|
||||
sw.write_string("\0\0\0"); // reserved zero
|
||||
sw.write_u8(0x0f); // NTLMSSP_REVISION_W2K3
|
||||
|
||||
self.nego_msg = sw.into_inner();
|
||||
}
|
||||
|
||||
pub fn generate_client_chal(&mut self, server_chal: &[u8]) {
|
||||
let mut reader = Reader::new(Input::from(server_chal));
|
||||
|
||||
let magic = read_utf8(&mut reader, 8);
|
||||
if magic != NTML_MAGIC {
|
||||
panic!("Unknown magic {:?}", magic);
|
||||
}
|
||||
|
||||
let respone = read_u32(&mut reader);
|
||||
if respone != 2 {
|
||||
panic!("Unknown response type {:?}", respone);
|
||||
}
|
||||
|
||||
// reader server info
|
||||
let target_name = NtlmMsgObj::new(&mut reader, server_chal);
|
||||
let nego_flag = read_u32(&mut reader);
|
||||
let server_challenge = reader.read_bytes(8).unwrap().as_slice_less_safe();
|
||||
let reserved = reader.read_bytes(8);
|
||||
let target_info = NtlmMsgObj::new(&mut reader, server_chal);
|
||||
let version = reader.read_bytes(8);
|
||||
self.server_info =
|
||||
NtlmServerInfo::new_from(&target_name, nego_flag, server_challenge, &target_info);
|
||||
|
||||
// build auth info
|
||||
if self.ntlm_v2 {
|
||||
let v = self.construct_authenticate_target_info();
|
||||
self.auth_info.insert("target_info", v);
|
||||
}
|
||||
let v = self.server_info.server_timestamp.as_ref().unwrap().to_vec();
|
||||
self.auth_info.insert("timestamp", v);
|
||||
let mut challenge = [0_u8; 8];
|
||||
for i in &mut challenge {
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
let x = ((random() as u32 * 212343) & 0xff) as u8;
|
||||
*i = x;
|
||||
}
|
||||
#[cfg(test)]
|
||||
{
|
||||
*i = 0;
|
||||
}
|
||||
}
|
||||
let v = challenge.to_vec();
|
||||
self.auth_info.insert("challenge", v);
|
||||
|
||||
/* LmChallengeResponse */
|
||||
let v = self.compute_lm_v2_response();
|
||||
self.auth_info.insert("lmv2", v);
|
||||
|
||||
/* NtChallengeResponse */
|
||||
let v = self.compute_ntlm_v2_response();
|
||||
self.auth_info.insert("ntlmv2", v);
|
||||
|
||||
/* KeyExchangeKey */
|
||||
let v = self.generate_key_exchange_key();
|
||||
self.auth_info.insert("exchange_key", v);
|
||||
|
||||
/* RandomSessionKey */
|
||||
let v = self.generate_random_session_key();
|
||||
self.auth_info.insert("random_session_key", v);
|
||||
|
||||
/* ExportedSessionKey */
|
||||
let v = self.generate_exported_session_key();
|
||||
self.auth_info.insert("exported_session_key", v);
|
||||
|
||||
/* EncryptedRandomSessionKey */
|
||||
let v = self.encrypt_random_session_key();
|
||||
self.auth_info.insert("encrypt_session_key", v);
|
||||
|
||||
/* Generate signing keys */
|
||||
let v = self.generate_client_signing_key();
|
||||
self.auth_info.insert("client_sign_key", v);
|
||||
let v = self.generate_server_signing_key();
|
||||
self.auth_info.insert("server_sign_key", v);
|
||||
|
||||
/* Generate sealing keys */
|
||||
let v = self.generate_client_sealing_key();
|
||||
self.auth_info.insert("client_seal_key", v);
|
||||
let v = self.generate_server_sealing_key();
|
||||
self.auth_info.insert("server_seal_key", v);
|
||||
|
||||
let chal_msg = Vec::new();
|
||||
let mut sw = StreamWriter::new(chal_msg);
|
||||
let mut nego_flags = 0;
|
||||
|
||||
if self.ntlm_v2 {
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_56;
|
||||
if self.send_version_info {
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
if self.use_mic {
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_TARGET_INFO;
|
||||
}
|
||||
|
||||
if self.send_workstation_name {
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED;
|
||||
}
|
||||
|
||||
if self.confidentiality {
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_SEAL;
|
||||
}
|
||||
|
||||
if self.server_info.server_nego & NTLMSSP_NEGOTIATE_KEY_EXCH > 0 {
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_KEY_EXCH;
|
||||
}
|
||||
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_128;
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY;
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_NTLM;
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_SIGN;
|
||||
nego_flags |= NTLMSSP_REQUEST_TARGET;
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_UNICODE;
|
||||
|
||||
if !self.domain.is_empty() {
|
||||
nego_flags |= NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED;
|
||||
}
|
||||
|
||||
let mut offset = 64;
|
||||
|
||||
if nego_flags & NTLMSSP_NEGOTIATE_VERSION > 0 {
|
||||
offset += 8; /* Version (8 bytes) */
|
||||
}
|
||||
|
||||
if self.use_mic {
|
||||
offset += 16; /* Message Integrity Check (16 bytes) */
|
||||
}
|
||||
|
||||
let domain_offset = offset;
|
||||
let user_offset = domain_offset + self.domain.len() * 2;
|
||||
let hostname_offset = user_offset + self.username.len() * 2;
|
||||
let lm_offset = hostname_offset + self.hostname.len() * 2;
|
||||
let nt_offset = lm_offset + self.auth_info.get("lmv2").unwrap().len();
|
||||
let random_key_offset = nt_offset + self.auth_info.get("ntlmv2").unwrap().len();
|
||||
let mut mic_offset = 0;
|
||||
|
||||
/* Message Header (12 bytes) */
|
||||
/* signature */
|
||||
sw.write_slice(NTML_MAGIC.as_bytes());
|
||||
/* type */
|
||||
sw.write_u32_le(3);
|
||||
|
||||
/* LmChallengeResponseFields (8 bytes) */
|
||||
sw.write_u16_le(self.auth_info.get("lmv2").unwrap().len() as u16);
|
||||
sw.write_u16_le(self.auth_info.get("lmv2").unwrap().len() as u16);
|
||||
sw.write_u32_le(lm_offset as u32);
|
||||
|
||||
/* NtChallengeResponseFields (8 bytes) */
|
||||
sw.write_u16_le(self.auth_info.get("ntlmv2").unwrap().len() as u16);
|
||||
sw.write_u16_le(self.auth_info.get("ntlmv2").unwrap().len() as u16);
|
||||
sw.write_u32_le(nt_offset as u32);
|
||||
|
||||
/* DomainNameFields (8 bytes) */
|
||||
sw.write_u16_le((self.domain.len() * 2) as u16);
|
||||
sw.write_u16_le((self.domain.len() * 2) as u16);
|
||||
sw.write_u32_le(domain_offset as u32);
|
||||
|
||||
/* UserNameFields (8 bytes) */
|
||||
sw.write_u16_le((self.username.len() * 2) as u16);
|
||||
sw.write_u16_le((self.username.len() * 2) as u16);
|
||||
sw.write_u32_le(user_offset as u32);
|
||||
|
||||
/* WorkstationFields (8 bytes) */
|
||||
sw.write_u16_le((self.hostname.len() * 2) as u16);
|
||||
sw.write_u16_le((self.hostname.len() * 2) as u16);
|
||||
sw.write_u32_le(hostname_offset as u32);
|
||||
|
||||
/* EncryptedRandomSessionKeyFields (8 bytes) */
|
||||
sw.write_u16_le(self.auth_info.get("encrypt_session_key").unwrap().len() as u16);
|
||||
sw.write_u16_le(self.auth_info.get("encrypt_session_key").unwrap().len() as u16);
|
||||
sw.write_u32_le(random_key_offset as u32);
|
||||
|
||||
/* NegotiateFlags (4 bytes) */
|
||||
sw.write_u32_le(nego_flags);
|
||||
|
||||
/* Version (8 bytes) */
|
||||
if nego_flags & NTLMSSP_NEGOTIATE_VERSION > 0 {
|
||||
sw.write_u8(6); // dwMajorVersion
|
||||
sw.write_u8(1); // dwMinorVersion
|
||||
sw.write_u16_le(7601); // dwBuildNumber
|
||||
sw.write_slice(&[0; 3]); // reserved zero
|
||||
sw.write_u8(0x0f); // NTLMSSP_REVISION_W2K3
|
||||
}
|
||||
|
||||
/* Message Integrity Check (16 bytes) */
|
||||
if self.use_mic {
|
||||
mic_offset = sw.get_inner().len();
|
||||
sw.write_slice(&[0; 16]); // reserved zero
|
||||
}
|
||||
|
||||
/* DomainName */
|
||||
if nego_flags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED > 0 {
|
||||
sw.write_slice(&to_unicode(&self.domain));
|
||||
}
|
||||
|
||||
/* UserName */
|
||||
sw.write_slice(&to_unicode(&self.username));
|
||||
|
||||
/* Workstation */
|
||||
if nego_flags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED > 0 {
|
||||
sw.write_slice(&to_unicode(&self.hostname));
|
||||
}
|
||||
|
||||
/* LmChallengeResponse */
|
||||
sw.write_slice(self.auth_info.get("lmv2").unwrap());
|
||||
|
||||
/* NtChallengeResponse */
|
||||
sw.write_slice(self.auth_info.get("ntlmv2").unwrap());
|
||||
|
||||
if nego_flags & NTLMSSP_NEGOTIATE_KEY_EXCH > 0 {
|
||||
/* EncryptedRandomSessionKey */
|
||||
sw.write_slice(self.auth_info.get("encrypt_session_key").unwrap());
|
||||
}
|
||||
|
||||
self.chal_msg = sw.into_inner();
|
||||
if self.use_mic {
|
||||
/* Message Integrity Check */
|
||||
let mic = hmac_md5(
|
||||
self.auth_info.get("exported_session_key").unwrap(),
|
||||
&[&self.nego_msg[..], server_chal, &self.chal_msg[..]].concat(),
|
||||
);
|
||||
for (i, m) in mic.into_iter().enumerate() {
|
||||
self.chal_msg[i + mic_offset] = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_client_auth(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn encrypt(&mut self, data: &[u8], seq: u32) -> Vec<u8> {
|
||||
let digest = hmac_md5(
|
||||
self.auth_info.get("client_sign_key").unwrap(),
|
||||
&[&seq.to_le_bytes(), data].concat(),
|
||||
);
|
||||
|
||||
let to_be_encrypted = if self.confidentiality {
|
||||
if self.encrypt.is_none() {
|
||||
self.encrypt = Some(Rc4::new(self.auth_info.get("client_seal_key").unwrap()))
|
||||
}
|
||||
let mut encrypted = vec![0; data.len()];
|
||||
self.encrypt.as_mut().unwrap().process(data, &mut encrypted);
|
||||
encrypted
|
||||
} else {
|
||||
// copy as is
|
||||
data.to_vec()
|
||||
};
|
||||
|
||||
// /* RC4-encrypt first 8 unsigned chars of digest */
|
||||
let mut chksum = vec![0; 8];
|
||||
self.encrypt
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.process(&digest[..8], &mut chksum);
|
||||
[
|
||||
&1_u32.to_le_bytes(),
|
||||
&chksum[..],
|
||||
&seq.to_le_bytes(),
|
||||
&to_be_encrypted,
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
|
||||
pub fn get_nego_msg(&self) -> &[u8] {
|
||||
&self.nego_msg
|
||||
}
|
||||
|
||||
pub fn get_chal_msg(&self) -> &[u8] {
|
||||
&self.chal_msg
|
||||
}
|
||||
|
||||
fn construct_authenticate_target_info(&mut self) -> Vec<u8> {
|
||||
let nb_domain_name = self.server_info.server_domain_name.as_ref();
|
||||
let nb_computer_name = self.server_info.server_computer_name.as_ref();
|
||||
let dns_domain_name = self.server_info.server_dns_domain_name.as_ref();
|
||||
let dns_computer_name = self.server_info.server_dns_computer_name.as_ref();
|
||||
let dns_tree_name = self.server_info.server_dns_tree_name.as_ref();
|
||||
let timestamp = self.server_info.server_timestamp.as_ref();
|
||||
let ret = Vec::new();
|
||||
let mut sw = StreamWriter::new(ret);
|
||||
|
||||
if let Some(nb_domain_name) = nb_domain_name {
|
||||
sw.write_u16_le(MsvAvNbDomainName);
|
||||
sw.write_u16_le(nb_domain_name.len() as u16 * 2);
|
||||
sw.write_slice(&to_unicode(nb_domain_name));
|
||||
}
|
||||
|
||||
if let Some(nb_computer_name) = nb_computer_name {
|
||||
sw.write_u16_le(MsvAvNbComputerName);
|
||||
sw.write_u16_le(nb_computer_name.len() as u16 * 2);
|
||||
sw.write_slice(&to_unicode(nb_computer_name));
|
||||
}
|
||||
|
||||
if let Some(dns_domain_name) = dns_domain_name {
|
||||
sw.write_u16_le(MsvAvDnsDomainName);
|
||||
sw.write_u16_le(dns_domain_name.len() as u16 * 2);
|
||||
sw.write_slice(&to_unicode(dns_domain_name));
|
||||
}
|
||||
|
||||
if let Some(dns_computer_name) = dns_computer_name {
|
||||
sw.write_u16_le(MsvAvDnsComputerName);
|
||||
sw.write_u16_le(dns_computer_name.len() as u16 * 2);
|
||||
sw.write_slice(&to_unicode(dns_computer_name));
|
||||
}
|
||||
|
||||
if let Some(dns_tree_name) = dns_tree_name {
|
||||
sw.write_u16_le(MsvAvDnsTreeName);
|
||||
sw.write_u16_le(dns_tree_name.len() as u16 * 2);
|
||||
sw.write_slice(&to_unicode(dns_tree_name));
|
||||
}
|
||||
|
||||
if let Some(timestamp) = timestamp {
|
||||
sw.write_u16_le(MsvAvTimestamp);
|
||||
sw.write_u16_le(8);
|
||||
sw.write_slice(timestamp);
|
||||
}
|
||||
|
||||
if self.use_mic {
|
||||
sw.write_u16_le(MsvAvFlags);
|
||||
sw.write_u16_le(4);
|
||||
sw.write_u32_le(MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK);
|
||||
}
|
||||
|
||||
/*
|
||||
* Extended Protection for Authentication:
|
||||
* http://blogs.technet.com/b/srd/archive/2009/12/08/extended-protection-for-authentication.aspx
|
||||
*/
|
||||
if !self.suppress_extended_protection {
|
||||
/*
|
||||
* SEC_CHANNEL_BINDINGS structure
|
||||
* http://msdn.microsoft.com/en-us/library/windows/desktop/dd919963/
|
||||
*/
|
||||
sw.write_u16_le(MsvChannelBindings);
|
||||
sw.write_u16_le(16);
|
||||
sw.write_slice(&[0; 16]);
|
||||
|
||||
if !self.hostname.is_empty() {
|
||||
sw.write_u16_le(MsvAvTargetName);
|
||||
sw.write_u16_le(self.hostname.len() as u16 * 2);
|
||||
sw.write_slice(&to_unicode(&self.hostname));
|
||||
}
|
||||
}
|
||||
|
||||
if self.ntlm_v2 {
|
||||
sw.write_u16_le(MsvAvEOL);
|
||||
sw.write_u16_le(0);
|
||||
}
|
||||
sw.into_inner()
|
||||
}
|
||||
|
||||
fn compute_ntlm_v2_hash(&mut self) -> Vec<u8> {
|
||||
if self.password.len() > 255 {
|
||||
panic!("Password too long");
|
||||
} else {
|
||||
let mut md4_ctx = Md4::new();
|
||||
md4_ctx.update(to_unicode(&self.password));
|
||||
let passwd_hash = md4_ctx.finalize();
|
||||
|
||||
hmac_md5(
|
||||
&passwd_hash,
|
||||
&to_unicode(&(self.username.to_uppercase() + &self.domain)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_lm_v2_response(&mut self) -> Vec<u8> {
|
||||
/* Compute the NTLMv2 hash */
|
||||
let ntlm_v2_hash = self.compute_ntlm_v2_hash();
|
||||
|
||||
/* Concatenate the server and client ServerChallengechallenges */
|
||||
let mut msg = hmac_md5(
|
||||
&ntlm_v2_hash,
|
||||
&[
|
||||
&self.server_info.server_chal[..],
|
||||
&self.auth_info.get("challenge").unwrap()[..],
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
|
||||
/* Concatenate the resulting HMAC-MD5 hash and the client challenge, giving
|
||||
* us the LMv2 response (24 bytes) */
|
||||
msg.extend_from_slice(self.auth_info.get("challenge").unwrap());
|
||||
msg
|
||||
}
|
||||
|
||||
fn compute_ntlm_v2_response(&mut self) -> Vec<u8> {
|
||||
let blob_c = Vec::new();
|
||||
let mut sw_c = StreamWriter::new(blob_c);
|
||||
let blob_s = Vec::new();
|
||||
let mut sw_s = StreamWriter::new(blob_s);
|
||||
let nt_proof = Vec::new();
|
||||
let mut sw_nt = StreamWriter::new(nt_proof);
|
||||
let ntlm_v2_hash = self.compute_ntlm_v2_hash();
|
||||
|
||||
/* Construct */
|
||||
sw_c.write_u8(1); /* RespType (1 byte) */
|
||||
sw_c.write_u8(1); /* HighRespType (1 byte) */
|
||||
sw_c.write_slice(&[0; 2]); /* Reserved1 (2 bytes) */
|
||||
sw_c.write_slice(&[0; 4]); /* Reserved2 (4 bytes) */
|
||||
sw_c.write_slice(&self.auth_info.get("timestamp").unwrap()[..8]); /* Timestamp (8 bytes) */
|
||||
sw_c.write_slice(self.auth_info.get("challenge").unwrap()); /* ClientChallenge (8 bytes) */
|
||||
sw_c.write_slice(&[0; 4]); /* Reserved3 (4 bytes) */
|
||||
sw_c.write_slice(self.auth_info.get("target_info").unwrap());
|
||||
|
||||
/* Concatenate server challenge with temp */
|
||||
sw_s.write_slice(&self.server_info.server_chal);
|
||||
sw_s.write_slice(sw_c.get_inner());
|
||||
|
||||
let nt_proof_msg = hmac_md5(&ntlm_v2_hash, sw_s.get_inner());
|
||||
|
||||
/* NtChallengeResponse, Concatenate NTProofStr with temp */
|
||||
sw_nt.write_slice(&nt_proof_msg);
|
||||
sw_nt.write_slice(sw_c.get_inner());
|
||||
|
||||
/* Compute SessionBaseKey, the HMAC-MD5 hash of NTProofStr using the NTLMv2
|
||||
* hash as the key */
|
||||
self.auth_info
|
||||
.insert("session_base_key", hmac_md5(&ntlm_v2_hash, &nt_proof_msg));
|
||||
|
||||
sw_nt.into_inner()
|
||||
}
|
||||
|
||||
fn generate_key_exchange_key(&mut self) -> Vec<u8> {
|
||||
self.auth_info.get("session_base_key").unwrap().clone()
|
||||
}
|
||||
|
||||
fn generate_random_session_key(&mut self) -> Vec<u8> {
|
||||
let mut rand = [0_u8; 16];
|
||||
for i in &mut rand {
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
let x = ((random() as u32 * 24635) & 0xff) as u8;
|
||||
*i = x;
|
||||
}
|
||||
#[cfg(test)]
|
||||
{
|
||||
*i = 0;
|
||||
}
|
||||
}
|
||||
rand.to_vec()
|
||||
}
|
||||
|
||||
fn generate_exported_session_key(&mut self) -> Vec<u8> {
|
||||
self.auth_info.get("random_session_key").unwrap().clone()
|
||||
}
|
||||
|
||||
fn encrypt_random_session_key(&mut self) -> Vec<u8> {
|
||||
rc4k(
|
||||
self.auth_info.get("exchange_key").unwrap(),
|
||||
self.auth_info.get("exported_session_key").unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
/* Generate signing keys */
|
||||
fn generate_signing_key(&mut self, magic: &[u8]) -> Vec<u8> {
|
||||
/* Concatenate ExportedSessionKey with sign magic */
|
||||
let mut hasher = Md5::new();
|
||||
hasher.update(
|
||||
[
|
||||
&self.auth_info.get("exported_session_key").unwrap()[..],
|
||||
magic,
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
hasher.finalize().to_vec()
|
||||
}
|
||||
|
||||
fn generate_client_signing_key(&mut self) -> Vec<u8> {
|
||||
self.generate_signing_key(CLIENT_SIGN_MAGIC)
|
||||
}
|
||||
|
||||
fn generate_server_signing_key(&mut self) -> Vec<u8> {
|
||||
self.generate_signing_key(SERVER_SIGN_MAGIC)
|
||||
}
|
||||
|
||||
fn generate_client_sealing_key(&mut self) -> Vec<u8> {
|
||||
self.generate_signing_key(CLIENT_SEAL_MAGIC)
|
||||
}
|
||||
|
||||
fn generate_server_sealing_key(&mut self) -> Vec<u8> {
|
||||
self.generate_signing_key(SERVER_SEAL_MAGIC)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_ntlm() {
|
||||
let mut ntlm = Ntlm::new("sonicwall", "sonicwall", "", "SRA-HTML5-RDP");
|
||||
ntlm.init(ISC_REQ_MUTUAL_AUTH | ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY);
|
||||
ntlm.generate_nego();
|
||||
ntlm.generate_client_chal(&[
|
||||
0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00,
|
||||
0x1e, 0x00, 0x38, 0x00, 0x00, 0x00, 0x35, 0x82, 0x8a, 0xe2, 0xde, 0xf9, 0x8e, 0xbc,
|
||||
0x63, 0xf8, 0xa3, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x00,
|
||||
0x98, 0x00, 0x56, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x61, 0x4a, 0x00, 0x00, 0x00, 0x0f,
|
||||
0x44, 0x00, 0x45, 0x00, 0x53, 0x00, 0x4b, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x50, 0x00,
|
||||
0x2d, 0x00, 0x4d, 0x00, 0x55, 0x00, 0x48, 0x00, 0x4d, 0x00, 0x42, 0x00, 0x51, 0x00,
|
||||
0x31, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x44, 0x00, 0x45, 0x00, 0x53, 0x00, 0x4b, 0x00,
|
||||
0x54, 0x00, 0x4f, 0x00, 0x50, 0x00, 0x2d, 0x00, 0x4d, 0x00, 0x55, 0x00, 0x48, 0x00,
|
||||
0x4d, 0x00, 0x42, 0x00, 0x51, 0x00, 0x31, 0x00, 0x01, 0x00, 0x1e, 0x00, 0x44, 0x00,
|
||||
0x45, 0x00, 0x53, 0x00, 0x4b, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x50, 0x00, 0x2d, 0x00,
|
||||
0x4d, 0x00, 0x55, 0x00, 0x48, 0x00, 0x4d, 0x00, 0x42, 0x00, 0x51, 0x00, 0x31, 0x00,
|
||||
0x04, 0x00, 0x1e, 0x00, 0x44, 0x00, 0x45, 0x00, 0x53, 0x00, 0x4b, 0x00, 0x54, 0x00,
|
||||
0x4f, 0x00, 0x50, 0x00, 0x2d, 0x00, 0x4d, 0x00, 0x55, 0x00, 0x48, 0x00, 0x4d, 0x00,
|
||||
0x42, 0x00, 0x51, 0x00, 0x31, 0x00, 0x03, 0x00, 0x1e, 0x00, 0x44, 0x00, 0x45, 0x00,
|
||||
0x53, 0x00, 0x4b, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x50, 0x00, 0x2d, 0x00, 0x4d, 0x00,
|
||||
0x55, 0x00, 0x48, 0x00, 0x4d, 0x00, 0x42, 0x00, 0x51, 0x00, 0x31, 0x00, 0x07, 0x00,
|
||||
0x08, 0x00, 0xd4, 0x0f, 0xe9, 0x0e, 0x3d, 0xe4, 0xd8, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
]);
|
||||
|
||||
let out_put = ntlm.get_chal_msg();
|
||||
|
||||
assert_eq!(
|
||||
out_put,
|
||||
&[
|
||||
0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00,
|
||||
0x18, 0x00, 0x84, 0x00, 0x00, 0x00, 0xfe, 0x00, 0xfe, 0x00, 0x9c, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x12, 0x00, 0x12, 0x00, 0x58, 0x00,
|
||||
0x00, 0x00, 0x1a, 0x00, 0x1a, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00,
|
||||
0x9a, 0x01, 0x00, 0x00, 0x35, 0xa2, 0x88, 0xe2, 0x06, 0x01, 0xb1, 0x1d, 0x00, 0x00,
|
||||
0x00, 0x0f, 0x41, 0x43, 0x6d, 0x1b, 0x0a, 0x90, 0x26, 0xf2, 0x19, 0x8b, 0xca, 0x5c,
|
||||
0xb7, 0xf5, 0x91, 0x3d, 0x73, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x63, 0x00,
|
||||
0x77, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x41, 0x00,
|
||||
0x2d, 0x00, 0x48, 0x00, 0x54, 0x00, 0x4d, 0x00, 0x4c, 0x00, 0x35, 0x00, 0x2d, 0x00,
|
||||
0x52, 0x00, 0x44, 0x00, 0x50, 0x00, 0x78, 0xb6, 0x42, 0xb8, 0xe1, 0x90, 0x21, 0xdf,
|
||||
0x21, 0xb6, 0x68, 0x79, 0xc9, 0x3c, 0x2b, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x17, 0xa8, 0x35, 0x50, 0x59, 0x07, 0x77, 0x2e, 0x1b, 0x09, 0xf6, 0x62,
|
||||
0xea, 0x97, 0x13, 0xce, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0x0f,
|
||||
0xe9, 0x0e, 0x3d, 0xe4, 0xd8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x44, 0x00, 0x45, 0x00, 0x53, 0x00,
|
||||
0x4b, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x50, 0x00, 0x2d, 0x00, 0x4d, 0x00, 0x55, 0x00,
|
||||
0x48, 0x00, 0x4d, 0x00, 0x42, 0x00, 0x51, 0x00, 0x31, 0x00, 0x01, 0x00, 0x1e, 0x00,
|
||||
0x44, 0x00, 0x45, 0x00, 0x53, 0x00, 0x4b, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x50, 0x00,
|
||||
0x2d, 0x00, 0x4d, 0x00, 0x55, 0x00, 0x48, 0x00, 0x4d, 0x00, 0x42, 0x00, 0x51, 0x00,
|
||||
0x31, 0x00, 0x04, 0x00, 0x1e, 0x00, 0x44, 0x00, 0x45, 0x00, 0x53, 0x00, 0x4b, 0x00,
|
||||
0x54, 0x00, 0x4f, 0x00, 0x50, 0x00, 0x2d, 0x00, 0x4d, 0x00, 0x55, 0x00, 0x48, 0x00,
|
||||
0x4d, 0x00, 0x42, 0x00, 0x51, 0x00, 0x31, 0x00, 0x03, 0x00, 0x1e, 0x00, 0x44, 0x00,
|
||||
0x45, 0x00, 0x53, 0x00, 0x4b, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x50, 0x00, 0x2d, 0x00,
|
||||
0x4d, 0x00, 0x55, 0x00, 0x48, 0x00, 0x4d, 0x00, 0x42, 0x00, 0x51, 0x00, 0x31, 0x00,
|
||||
0x07, 0x00, 0x08, 0x00, 0xd4, 0x0f, 0xe9, 0x0e, 0x3d, 0xe4, 0xd8, 0x01, 0x06, 0x00,
|
||||
0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00,
|
||||
0x1a, 0x00, 0x53, 0x00, 0x52, 0x00, 0x41, 0x00, 0x2d, 0x00, 0x48, 0x00, 0x54, 0x00,
|
||||
0x4d, 0x00, 0x4c, 0x00, 0x35, 0x00, 0x2d, 0x00, 0x52, 0x00, 0x44, 0x00, 0x50, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xf5, 0x7a, 0xcd, 0x84, 0x54, 0x55, 0x29, 0xfc, 0x8c, 0x0b,
|
||||
0x1d, 0x45, 0xb0, 0xbf, 0xba, 0x0a
|
||||
]
|
||||
);
|
||||
|
||||
let encrypt_key = ntlm.encrypt(
|
||||
[
|
||||
0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd0, 0xe1, 0x5f, 0xf5, 0x6d,
|
||||
0xbf, 0xa2, 0xbe, 0x52, 0x21, 0x13, 0x99, 0xae, 0x0b, 0x56, 0x08, 0x84, 0x46, 0x41,
|
||||
0xb1, 0x6a, 0x81, 0xbb, 0xe8, 0xff, 0x6e, 0xea, 0xa4, 0xc5, 0x57, 0x58, 0x85, 0xf7,
|
||||
0x12, 0xef, 0xaa, 0x3b, 0x57, 0x24, 0xf5, 0x2d, 0x59, 0xaa, 0xd6, 0xd7, 0x6b, 0xcd,
|
||||
0x62, 0x9f, 0x29, 0x7a, 0x65, 0x98, 0x42, 0xf0, 0x3e, 0xd8, 0x13, 0x45, 0x1d, 0xe0,
|
||||
0xd8, 0x19, 0x8c, 0x57, 0xd9, 0x91, 0xb8, 0xda, 0xed, 0x2e, 0x83, 0xf0, 0x34, 0x21,
|
||||
0xae, 0x36, 0x92, 0x0b, 0x3b, 0xa8, 0x01, 0x7a, 0xdd, 0x60, 0xd2, 0x17, 0x57, 0x2b,
|
||||
0x5e, 0xac, 0xf0, 0x5c, 0x3d, 0x73, 0x2a, 0x1e, 0xdd, 0x7c, 0xbc, 0x70, 0xeb, 0xdd,
|
||||
0x63, 0x58, 0x00, 0x16, 0x36, 0xf3, 0x0b, 0x48, 0x40, 0x79, 0xce, 0x6f, 0x52, 0xee,
|
||||
0x42, 0xfa, 0x0f, 0xed, 0xd0, 0xf4, 0x50, 0x73, 0xa6, 0x88, 0xce, 0x6e, 0x1a, 0x3b,
|
||||
0x69, 0x73, 0x86, 0x1d, 0x89, 0x21, 0x35, 0x97, 0x1e, 0x94, 0xab, 0xbe, 0xc4, 0x2b,
|
||||
0x4b, 0x42, 0x5e, 0x25, 0x26, 0xe5, 0x0e, 0x4e, 0x31, 0xfc, 0x7f, 0xf6, 0xfe, 0xda,
|
||||
0x44, 0x27, 0xe3, 0xde, 0xfa, 0xf1, 0xdd, 0x58, 0x66, 0x4a, 0x35, 0xf8, 0x03, 0x34,
|
||||
0x2b, 0x7a, 0xa9, 0x42, 0xfa, 0x46, 0xb2, 0xbd, 0xfb, 0x4c, 0x78, 0x66, 0xd9, 0xd1,
|
||||
0xac, 0x47, 0xf3, 0x02, 0xff, 0x44, 0xa7, 0x87, 0x26, 0x0c, 0xd3, 0xe6, 0x2c, 0xeb,
|
||||
0x4c, 0x4b, 0x51, 0x3f, 0xc6, 0x25, 0x8c, 0x22, 0x4a, 0xd2, 0xaa, 0x86, 0x73, 0xc4,
|
||||
0x90, 0x2d, 0xd3, 0xe3, 0x7a, 0xa8, 0x2b, 0x37, 0xb1, 0x5e, 0x0e, 0x31, 0x0b, 0x27,
|
||||
0x14, 0x0a, 0x8d, 0x1f, 0xed, 0xde, 0xb3, 0x19, 0xa8, 0x08, 0x63, 0x3d, 0xaf, 0x52,
|
||||
0xff, 0x38, 0xef, 0x54, 0x0d, 0xb9, 0x7e, 0xc9, 0x6f, 0x07, 0x30, 0x67, 0xe9, 0x02,
|
||||
0x03, 0x01, 0x00, 0x01,
|
||||
]
|
||||
.as_ref(),
|
||||
0,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&encrypt_key,
|
||||
&[
|
||||
0x01, 0x00, 0x00, 0x00, 0xa4, 0xe3, 0x81, 0x3f, 0xff, 0x39, 0x8d, 0xb4, 0x00, 0x00,
|
||||
0x00, 0x00, 0x21, 0x95, 0xdd, 0xca, 0x85, 0x92, 0xd4, 0x13, 0x20, 0x66, 0x44, 0xf8,
|
||||
0x52, 0x39, 0x53, 0x29, 0xea, 0x75, 0x57, 0x90, 0x6e, 0x54, 0x33, 0x2b, 0x09, 0xd7,
|
||||
0x52, 0x18, 0xf8, 0xde, 0x2b, 0x85, 0x13, 0x4f, 0x06, 0x57, 0x17, 0x6b, 0x89, 0x35,
|
||||
0xe0, 0x9d, 0x1c, 0x41, 0xad, 0xf1, 0xaf, 0xd2, 0x9c, 0xe7, 0x67, 0x3e, 0xe5, 0x2a,
|
||||
0xd0, 0xa3, 0x9e, 0x53, 0x12, 0xca, 0xc3, 0x74, 0x19, 0x58, 0x7f, 0x55, 0x5f, 0x71,
|
||||
0x2d, 0x5f, 0x88, 0x1e, 0x63, 0xc1, 0x98, 0xba, 0xfd, 0x2f, 0x42, 0x15, 0xe8, 0xf6,
|
||||
0xaf, 0xc3, 0x48, 0xa8, 0x4f, 0x8c, 0xab, 0x8a, 0x0f, 0xef, 0x12, 0xa6, 0x53, 0x5d,
|
||||
0x01, 0x80, 0xf6, 0xc7, 0x61, 0x71, 0x39, 0xb4, 0x09, 0xbb, 0x5d, 0xc9, 0x24, 0x07,
|
||||
0x66, 0x73, 0xa9, 0x03, 0x8d, 0x68, 0x8f, 0xf4, 0x78, 0xe5, 0x71, 0x5a, 0x69, 0x7f,
|
||||
0x06, 0xae, 0x76, 0x92, 0x6e, 0x58, 0xc7, 0xdc, 0xeb, 0x8a, 0x8a, 0x84, 0xa4, 0x22,
|
||||
0x54, 0x58, 0x05, 0xfd, 0x4c, 0xf0, 0xb8, 0x68, 0x41, 0x52, 0x64, 0xc6, 0xc7, 0x41,
|
||||
0x54, 0x0f, 0xb8, 0xa0, 0x36, 0xe2, 0x86, 0xa9, 0x8b, 0x98, 0x50, 0x6d, 0x69, 0xad,
|
||||
0xd2, 0xdc, 0x0b, 0x1a, 0xff, 0x9e, 0xca, 0xd7, 0x74, 0x39, 0x7a, 0xc0, 0xa8, 0x30,
|
||||
0x9b, 0xed, 0x52, 0x5d, 0xc1, 0xde, 0x53, 0x87, 0x1d, 0x13, 0xd4, 0xc0, 0x54, 0xa9,
|
||||
0x0a, 0x11, 0xbe, 0xc1, 0x74, 0x8e, 0x4a, 0x99, 0xdf, 0xe7, 0x33, 0x8a, 0xd9, 0x2b,
|
||||
0x70, 0x10, 0x9e, 0xa4, 0xd9, 0x42, 0x99, 0xfe, 0x6c, 0x49, 0x6c, 0xf0, 0x37, 0x99,
|
||||
0xfc, 0x4f, 0x51, 0x35, 0x56, 0x47, 0x7e, 0xa7, 0x17, 0x25, 0xee, 0x6e, 0x35, 0xea,
|
||||
0x9f, 0x5f, 0xf3, 0xd7, 0xd0, 0x31, 0xd2, 0x79, 0x68, 0xbc, 0x4f, 0xd7, 0x4e, 0xae,
|
||||
0x28, 0x24, 0xb8, 0x2c, 0xe2, 0x6e, 0xb6, 0x3f, 0xee, 0x8f, 0xa9, 0x33, 0x57, 0xf0,
|
||||
0x39, 0x65, 0x4f, 0xfe, 0xf7, 0x40
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
64
webrdp/src/rdp/protocol/nla/rc4.rs
Normal file
64
webrdp/src/rdp/protocol/nla/rc4.rs
Normal file
@ -0,0 +1,64 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2019 Sylvain Peyrefitte
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
pub struct Rc4 {
|
||||
i: u8,
|
||||
j: u8,
|
||||
state: [u8; 256],
|
||||
}
|
||||
|
||||
impl Rc4 {
|
||||
pub fn new(key: &[u8]) -> Rc4 {
|
||||
assert!(!key.is_empty() && key.len() <= 256);
|
||||
let mut rc4 = Rc4 {
|
||||
i: 0,
|
||||
j: 0,
|
||||
state: [0; 256],
|
||||
};
|
||||
for (i, x) in rc4.state.iter_mut().enumerate() {
|
||||
*x = i as u8;
|
||||
}
|
||||
let mut j: u8 = 0;
|
||||
for i in 0..256 {
|
||||
j = j
|
||||
.wrapping_add(rc4.state[i])
|
||||
.wrapping_add(key[i % key.len()]);
|
||||
rc4.state.swap(i, j as usize);
|
||||
}
|
||||
rc4
|
||||
}
|
||||
fn next(&mut self) -> u8 {
|
||||
self.i = self.i.wrapping_add(1);
|
||||
self.j = self.j.wrapping_add(self.state[self.i as usize]);
|
||||
self.state.swap(self.i as usize, self.j as usize);
|
||||
|
||||
self.state
|
||||
[(self.state[self.i as usize].wrapping_add(self.state[self.j as usize])) as usize]
|
||||
}
|
||||
|
||||
pub fn process(&mut self, input: &[u8], output: &mut [u8]) {
|
||||
assert!(input.len() == output.len());
|
||||
for (x, y) in input.iter().zip(output.iter_mut()) {
|
||||
*y = *x ^ self.next();
|
||||
}
|
||||
}
|
||||
}
|
140
webrdp/src/rdp/protocol/x224.rs
Normal file
140
webrdp/src/rdp/protocol/x224.rs
Normal file
@ -0,0 +1,140 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use super::super::rdp_impl::RdpInner;
|
||||
use super::super::*;
|
||||
|
||||
const RDP_X224_VER: u8 = 3;
|
||||
const RDP_X224_LEN: u8 = 0x13;
|
||||
const TPTK_HDR_LEN: u8 = 4;
|
||||
const RDP_NEG_REQ: u8 = 1;
|
||||
const RDP_NEG_RSP: u8 = 2;
|
||||
const RDP_NEG_FAIL: u8 = 3;
|
||||
|
||||
const PROTOCOL_RDP: u32 = 0;
|
||||
const PROTOCOL_SSL: u32 = 1;
|
||||
const PROTOCOL_HYBRID: u32 = 2;
|
||||
const PROTOCOL_RDSTLS: u32 = 4;
|
||||
const PROTOCOL_HYBRID_EX: u32 = 8;
|
||||
const PROTOCOL_RDSAAD: u32 = 16;
|
||||
|
||||
const SSL_REQUIRED_BY_SERVER: u32 = 0x00000001;
|
||||
const SSL_REQUIRED_BY_SERVER_MSG: &str = "The server requires that the client support Enhanced RDP Security (section 5.4) with either TLS 1.0, 1.1 or 1.2 (section 5.4.5.1) or CredSSP (section 5.4.5.2). If only CredSSP was requested then the server only supports TLS.";
|
||||
|
||||
const SSL_NOT_ALLOWED_BY_SERVER: u32 = 0x00000002;
|
||||
const SSL_NOT_ALLOWED_BY_SERVER_MSG: &str = "The server is configured to only use Standard RDP Security mechanisms (section 5.3) and does not support any External Security Protocols (section 5.4.5).";
|
||||
|
||||
const SSL_CERT_NOT_ON_SERVER: u32 = 0x00000003;
|
||||
const SSL_CERT_NOT_ON_SERVER_MSG: &str = "The server does not possess a valid authentication certificate and cannot initialize the External Security Protocol Provider (section 5.4.5).";
|
||||
|
||||
const INCONSISTENT_FLAGS: u32 = 0x00000004;
|
||||
const INCONSISTENT_FLAGS_MSG: &str = "The list of requested security protocols is not consistent with the current security protocol in effect. This error is only possible when the Direct Approach (sections 5.4.2.2 and 1.3.1.2) is used and an External Security Protocol (section 5.4.5) is already being used.";
|
||||
|
||||
const HYBRID_REQUIRED_BY_SERVER: u32 = 0x00000005;
|
||||
const HYBRID_REQUIRED_BY_SERVER_MSG: &str = "The server requires that the client support Enhanced RDP Security (section 5.4) with CredSSP (section 5.4.5.2).";
|
||||
|
||||
const SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER: u32 = 0x00000006;
|
||||
const SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER_MSG: &str = "The server requires that the client support Enhanced RDP Security (section 5.4) with TLS 1.0, 1.1 or 1.2 (section 5.4.5.1) and certificate-based client authentication.<4>";
|
||||
|
||||
pub struct X224 {
|
||||
on_connect: ConnectCb,
|
||||
on_fail: FailCb,
|
||||
}
|
||||
|
||||
impl X224 {
|
||||
pub fn new(on_connect: ConnectCb, on_fail: FailCb) -> Self {
|
||||
Self {
|
||||
on_connect,
|
||||
on_fail,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Engine for X224 {
|
||||
fn hello(&mut self, rdp: &mut RdpInner) {
|
||||
// send X.224 request
|
||||
// Client X.224 Connection Request PDU
|
||||
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/18a27ef9-6f9a-4501-b000-94b1fe3c2c10
|
||||
|
||||
// tpktHeader (4 bytes): A TPKT Header
|
||||
// https://www.itu.int/rec/T-REC-T.123-200701-I/en
|
||||
rdp.writer.write_u8(RDP_X224_VER); // version number
|
||||
rdp.writer.write_u8(0); // reversed
|
||||
rdp.writer.write_u8(0); // length (MSB)
|
||||
rdp.writer.write_u8(RDP_X224_LEN); // length (LSB)
|
||||
|
||||
// x224Crq (7 bytes):
|
||||
// An X.224 Class 0 Connection Request transport protocol data unit (TPDU).
|
||||
// https://www.itu.int/rec/T-REC-X.224-199511-I/en section 13.3
|
||||
rdp.writer.write_u8(RDP_X224_LEN - TPTK_HDR_LEN - 1); // Length indicator
|
||||
rdp.writer.write_u8(0b11100000); // Connection request(4MSB) | Initial credit allocation(4LSB)
|
||||
rdp.writer.write_u8(0); // DST-REF
|
||||
rdp.writer.write_u8(0); // DST-REF
|
||||
rdp.writer.write_u8(0); // SRC-REF
|
||||
rdp.writer.write_u8(0); // SRC-REF
|
||||
rdp.writer.write_u8(0); // CLASS OPTION
|
||||
|
||||
// rdpNegReq (8 bytes):
|
||||
// An optional RDP Negotiation Request (section 2.2.1.1.1) structure.
|
||||
// The length of this field is included in the X.224 Connection Request Length Indicator field.
|
||||
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/902b090b-9cb3-4efc-92bf-ee13373371e3
|
||||
rdp.writer.write_u8(RDP_NEG_REQ); // type TYPE_RDP_NEG_REQ
|
||||
rdp.writer.write_u8(0); // flags
|
||||
rdp.writer.write_u8(8); // length (LSB)
|
||||
rdp.writer.write_u8(0); // length (MSB)
|
||||
rdp.writer.write_u32_le(PROTOCOL_SSL | PROTOCOL_HYBRID); // requestedProtocols: TLS | CredSSP
|
||||
|
||||
rdp.wait(RDP_X224_LEN as usize);
|
||||
}
|
||||
|
||||
fn do_input(&mut self, rdp: &mut RdpInner) {
|
||||
// tpktHeader (4 bytes): A TPKT Header
|
||||
// https://www.itu.int/rec/T-REC-T.123-200701-I/en
|
||||
let _ = rdp.reader.read_u32_be();
|
||||
|
||||
// x224Ccf (7 bytes): An X.224 Class 0 Connection Confirm TPDU
|
||||
// https://www.itu.int/rec/T-REC-X.224-199511-I/en section 13.4
|
||||
let _ = rdp.reader.read_u32_be();
|
||||
let _ = rdp.reader.read_u16_be();
|
||||
let _ = rdp.reader.read_u8();
|
||||
|
||||
// rdpNegData (8 bytes):
|
||||
// An optional RDP Negotiation Response structure or an optional RDP Negotiation Failure structure.
|
||||
// The length of this field is included in the X.224 Connection Confirm Length Indicator field.
|
||||
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/b2975bdc-6d56-49ee-9c57-f2ff3a0b6817
|
||||
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/1b3920e7-0116-4345-bc45-f2c4ad012761
|
||||
let type_ = rdp.reader.read_u8();
|
||||
let flags = rdp.reader.read_u8();
|
||||
let length = rdp.reader.read_u16_le();
|
||||
assert!(length == 8);
|
||||
let payload = rdp.reader.read_u32_le();
|
||||
|
||||
match type_ {
|
||||
RDP_NEG_RSP => {
|
||||
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/b2975bdc-6d56-49ee-9c57-f2ff3a0b6817
|
||||
let selected_protocal = payload;
|
||||
if selected_protocal & PROTOCOL_HYBRID == 0 {
|
||||
(self.on_fail)(rdp, "Server does not support nla");
|
||||
}
|
||||
rdp.nla(true);
|
||||
(self.on_connect)(rdp);
|
||||
}
|
||||
RDP_NEG_FAIL => {
|
||||
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/1b3920e7-0116-4345-bc45-f2c4ad012761
|
||||
assert!(flags == 0);
|
||||
let fail_reason = payload;
|
||||
match fail_reason {
|
||||
SSL_REQUIRED_BY_SERVER => (self.on_fail)(rdp, SSL_REQUIRED_BY_SERVER_MSG),
|
||||
SSL_NOT_ALLOWED_BY_SERVER => (self.on_fail)(rdp, SSL_NOT_ALLOWED_BY_SERVER_MSG),
|
||||
SSL_CERT_NOT_ON_SERVER => (self.on_fail)(rdp, SSL_CERT_NOT_ON_SERVER_MSG),
|
||||
INCONSISTENT_FLAGS => (self.on_fail)(rdp, INCONSISTENT_FLAGS_MSG),
|
||||
HYBRID_REQUIRED_BY_SERVER => (self.on_fail)(rdp, HYBRID_REQUIRED_BY_SERVER_MSG),
|
||||
SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER => {
|
||||
(self.on_fail)(rdp, SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER_MSG)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
140
webrdp/src/rdp/rdp_impl.rs
Normal file
140
webrdp/src/rdp/rdp_impl.rs
Normal file
@ -0,0 +1,140 @@
|
||||
use crate::{console_log, log};
|
||||
|
||||
use super::protocol::*;
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
Init,
|
||||
X224,
|
||||
Nla,
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
pub struct RdpInner {
|
||||
state: State,
|
||||
engine: Option<Box<dyn super::Engine>>,
|
||||
pub reader: StreamReader,
|
||||
pub writer: StreamWriter,
|
||||
outs: Vec<RdpOutput>,
|
||||
require: usize,
|
||||
need_nla: bool,
|
||||
}
|
||||
|
||||
impl RdpInner {
|
||||
pub fn new() -> Self {
|
||||
RdpInner {
|
||||
state: State::Init,
|
||||
engine: None,
|
||||
reader: StreamReader::new(Vec::with_capacity(10), 0),
|
||||
writer: StreamWriter::new(Vec::with_capacity(1024)),
|
||||
outs: Vec::with_capacity(10),
|
||||
require: 0,
|
||||
need_nla: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
if self.engine.is_some() {
|
||||
panic!("inited");
|
||||
}
|
||||
self.move_next();
|
||||
}
|
||||
|
||||
pub fn do_input(&mut self, buf: Vec<u8>) {
|
||||
if let State::Disconnected = self.state {
|
||||
return;
|
||||
};
|
||||
self.reader.append(buf);
|
||||
while self.reader.remain() >= self.require {
|
||||
let mut handler = self.engine.take().unwrap();
|
||||
|
||||
handler.do_input(self);
|
||||
if self.engine.is_none() {
|
||||
self.engine = Some(handler);
|
||||
}
|
||||
|
||||
// console_log!("left {}, require {}", self.reader.remain(), self.require);
|
||||
if let State::Disconnected = self.state {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_output(&mut self) -> Option<Vec<RdpOutput>> {
|
||||
if self.outs.is_empty() && self.writer.buf.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut out = Vec::with_capacity(self.outs.len());
|
||||
// console_log!("Get {} output", self.outs.len());
|
||||
for o in self.outs.drain(..) {
|
||||
out.push(o);
|
||||
}
|
||||
if !self.writer.buf.is_empty() {
|
||||
out.push(RdpOutput::WsBuf(self.writer.buf.clone()));
|
||||
self.writer.buf.clear();
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
self.state = State::Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
impl RdpInner {
|
||||
fn move_next(&mut self) {
|
||||
console_log!("State move from {:?} to the next", self.state);
|
||||
match self.state {
|
||||
State::Init => {
|
||||
let mut x224 = x224::X224::new(Self::move_next, Self::disconnect_with_err);
|
||||
x224.hello(self);
|
||||
self.engine = Some(Box::new(x224));
|
||||
self.state = State::X224;
|
||||
}
|
||||
State::X224 => {
|
||||
if self.need_nla {
|
||||
self.start_tls();
|
||||
let mut nla = nla::Nla::new(
|
||||
Self::move_next,
|
||||
Self::disconnect_with_err,
|
||||
"sonicwall",
|
||||
"sonicwall",
|
||||
"",
|
||||
"webrdp",
|
||||
);
|
||||
nla.hello(self);
|
||||
self.engine = Some(Box::new(nla));
|
||||
self.state = State::Nla;
|
||||
} else {
|
||||
unimplemented!("Only nla is supported now");
|
||||
}
|
||||
}
|
||||
State::Nla => {}
|
||||
State::Disconnected => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn disconnect_with_err(&mut self, err: &str) {
|
||||
console_log!("{:#?}", err);
|
||||
self.state = State::Disconnected;
|
||||
self.outs.push(RdpOutput::Err(err.to_string()));
|
||||
}
|
||||
|
||||
pub fn need_wait(&mut self, len: usize) -> bool {
|
||||
self.reader.remain() < len
|
||||
}
|
||||
|
||||
pub fn wait(&mut self, len: usize) {
|
||||
self.require = len;
|
||||
}
|
||||
|
||||
pub fn nla(&mut self, need: bool) {
|
||||
self.need_nla = need;
|
||||
}
|
||||
|
||||
fn start_tls(&mut self) {
|
||||
self.outs.push(RdpOutput::RequireSSL);
|
||||
}
|
||||
}
|
38
webrdp/src/rdp/x11cursor.rs
Normal file
38
webrdp/src/rdp/x11cursor.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use super::MouseEventType;
|
||||
|
||||
pub struct MouseUtils {
|
||||
down: bool,
|
||||
mask: u8,
|
||||
}
|
||||
|
||||
impl MouseUtils {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
down: false,
|
||||
mask: 0,
|
||||
}
|
||||
}
|
||||
pub fn get_mouse_sym(
|
||||
&mut self,
|
||||
event: web_sys::MouseEvent,
|
||||
et: MouseEventType,
|
||||
) -> (u16, u16, u8) {
|
||||
let x: u16 = event.offset_x().try_into().unwrap_or(0);
|
||||
let y: u16 = event.offset_y().try_into().unwrap_or(0);
|
||||
let mask: u8 = (event.button() << 1).try_into().unwrap_or(0);
|
||||
|
||||
match et {
|
||||
MouseEventType::Down => {
|
||||
self.down = true;
|
||||
self.mask = self.down as u8 | mask;
|
||||
}
|
||||
MouseEventType::Up => {
|
||||
self.down = false;
|
||||
self.mask = self.down as u8 & (!mask);
|
||||
}
|
||||
|
||||
MouseEventType::Move => {}
|
||||
}
|
||||
(x, y, self.mask)
|
||||
}
|
||||
}
|
1950
webrdp/src/rdp/x11keyboard.rs
Normal file
1950
webrdp/src/rdp/x11keyboard.rs
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user