Redirect stdio over TLS

Hi

Currently I am trying to redirect stdio over tokio_native_tls::TlsStream to my remote ncat instance but the functions I searched requires a socket file descriptor instead. I am able to redirect stdio over tokio::net::TcpStream but not TlsStream.

Is there a way to redirect stdio over a TLS connection?

You could create a pipe. One end becomes the new stdin. The other end is read by a background thread and sent over TLS.

Hi @bjorn3

This is my understanding, not sure if it is correct.

  • Program's main thread creates a pipe to a second thread

  • Program's second thread sends data that it receives to the remote ncat instance over TLS

  • Data from the remote ncat instance is sent back to the second thread and it pipes the data back to the main thread

main thread <---- pipe ----> second thread <---- TlsStream ----> ncat

So the second thread is like a proxy?

Yes. You will need two pipes though if you want bidirectional communication, as a pipe only works one direction. By the way is there a reason you can't just read/write the TlsStream directly without the indirection of stdin/stdout?

Hi

I am trying to replicate the "-e" function of ncat where command execution can be redirected over to my remote ncat instance.

I was able to do this with TcpStream using dup2 but not sure how it can be done with TlsStream.

Anything wrong with two joined copy futures?
(Not thought of how todo stderr though.)

Hi @bjorn3

I am having problems trying to test the idea you mentioned. Is there any small code snippets that I can start with?

Hi @jonh

I am not too sure what you meant by that. Is there any example that I can work on and test?

Hi @bjorn3

In another thread, someone has helped to solve the part about piping the input/output between the local processes (my program and a process running "sh"). Now I am trying to solve the part about forwarding the data over TLS to the remote ncat instance and also to receive data from the remote ncat instance and forward it to the "sh" process.

Based on the answers others have provided, I have this

		let mut command_stdin = command_output.stdin.unwrap();
		println!("command_stdin {}", command_stdin.as_raw_fd());
		let copy_stdin_thread = std::thread::spawn(move || {
			std::io::copy(&mut std::io::stdin(), &mut command_stdin);
		});
		
		let mut command_stdout = command_output.stdout.unwrap();
		println!("command_stdout {}", command_stdout.as_raw_fd());
		let copy_stdout_thread = std::thread::spawn(move || {
			std::io::copy(&mut command_stdout, &mut std::io::stdout());
		});

		let mut command_stderr = command_output.stderr.unwrap();
		println!("command_stderr {}", command_stderr.as_raw_fd());
		let copy_stderr_thread = std::thread::spawn(move || {
			std::io::copy(&mut command_stderr, &mut std::io::stderr());
		});

		copy_stdin_thread.join().unwrap();
		copy_stdout_thread.join().unwrap();
		copy_stderr_thread.join().unwrap();

		let (tls_read, tls_write) = tokio::io::split(tls_socket);

		let read_future = read_pipe(tls_read);
		let write_future = write_pipe(tls_write);

		futures::join!(read_future,write_future);

However, I don't think it works this way. What functions/crates should I explore regarding this forwarding/receiving of data over TLS?

EDIT 3 Nov 2020

After getting help from other forum users, I managed to pipe the stdio between my program and a "sh" process. However, any other input/output I tried after that to send over TLS to the remote ncat instance is not working as I don't see it in the remote ncat's terminal.

It seems that after the stdio is redirected, I am having problems trying to verify if any other redirection is working after that as I could not see any output anywhere.

What should be the correct approach to get output from "sh" and send it over TLS and to receive commands over TLS from ncat and send it to "sh" for execution?

image

I am trying to accomplish something similar to what this author has done (https://nickb.dev/blog/writing-a-high-performance-tls-terminating-proxy-in-rust).

		let mut command_output = std::process::Command::new("/bin/sh")
									.stdin(Stdio::piped())
									.stdout(Stdio::piped())
									.stderr(Stdio::piped())
									.spawn()
									.expect("cannot execute command");

		let mut command_stdin = command_output.stdin.unwrap();
		let copy_stdin_thread = std::thread::spawn(move || {
			std::io::copy(&mut std::io::stdin(), &mut command_stdin);
		});
		
		let mut command_stdout = command_output.stdout.unwrap();
		let copy_stdout_thread = std::thread::spawn(move || {
			std::io::copy(&mut command_stdout, &mut std::io::stdout());
		});

		let mut command_stderr = command_output.stderr.unwrap();
		let copy_stderr_thread = std::thread::spawn(move || {
			std::io::copy(&mut command_stderr, &mut std::io::stderr());
		});

		copy_stdin_thread.join().unwrap();
		copy_stdout_thread.join().unwrap();
		copy_stderr_thread.join().unwrap();

        // Does not seem to work or perhaps I was unable to see the output?
		tokio::io::copy(&mut tokio::io::stdin(),&mut tls_socket).await;
		tokio::io::copy(&mut tls_socket, &mut tokio::io::stdout()).await;
		tokio::io::copy(&mut tls_socket, &mut tokio::io::stderr()).await;

A user has answered my question on Stack Overflow. I am sharing the link here in case it is useful to others.