mirror of https://github.com/deavmi/cryptstream
263 lines
7.4 KiB
D
263 lines
7.4 KiB
D
module cryptstream.streams.client;
|
|
|
|
import river.core : RiverStream = Stream, StreamException, StreamError;
|
|
import core.thread : Thread, dur;
|
|
import botan.tls.client;
|
|
|
|
|
|
// TODO: This should be a kind-of `RiverStream`, but
|
|
// ... we defs can take IN a stream to use as the underlying
|
|
// ... connection upon which the TLS data will be sent over
|
|
public class CryptClient : RiverStream
|
|
{
|
|
/**
|
|
* Underlying stream to use for TLS-encrypted communication
|
|
*/
|
|
private RiverStream stream;
|
|
|
|
/**
|
|
* Reads bytes from the underlying thread to pass
|
|
* up to `receivedData()`
|
|
*/
|
|
private Thread streamReader;
|
|
|
|
/**
|
|
* Botan TLS client
|
|
*/
|
|
private TLSClient botanClient;
|
|
|
|
// TODO: Setup
|
|
private TLSSessionManager sessionManager = new TLSSessionManagerNoop();
|
|
private TLSCredentialsManager credentialManager;
|
|
|
|
import cryptstream.streams.testingPolicy;
|
|
private TLSPolicy policy = new TestingPolicy();
|
|
private RandomNumberGenerator rng;
|
|
|
|
/**
|
|
* Constructs a new TLS-enabled client stream
|
|
* Params:
|
|
* stream =
|
|
*/
|
|
this(RiverStream stream)
|
|
{
|
|
this.stream = stream;
|
|
|
|
this.rng = RandomNumberGenerator.makeRng();
|
|
// this.sessionManager = new TLSSessionManagerInMemory(rng);
|
|
|
|
import cryptstream.streams.credmanager : CredManager;
|
|
this.credentialManager = new CredManager();
|
|
|
|
// FIXME: Currently we are crashing with a segmentation fault here
|
|
this.botanClient = new TLSClient(&tlsOutputHandler, &decryptedInputHandler, &tlsAlertHandler, &tlsHandshakeHandler,
|
|
sessionManager, credentialManager, policy, rng);
|
|
|
|
// TODO: Insert code to init using botan OVER `stream`
|
|
|
|
this.plainTextReceivedLock = new Mutex();
|
|
|
|
|
|
// TODO: Start a thread which can read from the socket
|
|
// ... and then injct data to the Botan client to be decrypted
|
|
// ... using `receivedData()`
|
|
this.streamReader = new Thread(&streamReaderWorker);
|
|
this.streamReader.start();
|
|
|
|
// TODO: Add method which is passed to the BotanClient constructor
|
|
// ... above which is called upon decrypting a tls record
|
|
// ... then place this into a buffer here which our
|
|
// ... `read/readFully` can read from
|
|
}
|
|
|
|
private void streamReaderWorker()
|
|
{
|
|
// TODO: Make this size configurable
|
|
byte[500] readInto;
|
|
while(true)
|
|
{
|
|
ulong receivedAmount = stream.read(readInto);
|
|
|
|
version(unittest)
|
|
{
|
|
import std.stdio;
|
|
import std.conv : to;
|
|
writeln("streamReaderWorker(client-side): Transferring "~to!(string)(receivedAmount)~" many bytes over to Botan client...");
|
|
writeln("streamReaderWorker(client-side): The bytes are: "~to!(string)(readInto[0..receivedAmount]));
|
|
// writeln("streamReaderWorker(): The bytes are (as string): "~cast(string[])readInto[0..receivedAmount]);
|
|
|
|
}
|
|
|
|
// TODO: Use the hint byte count returned?
|
|
botanClient.receivedData(cast(ubyte*)readInto.ptr, receivedAmount);
|
|
}
|
|
}
|
|
|
|
private bool tlsHandshakeHandler(in TLSSession session)
|
|
{
|
|
// TODO: Implement me
|
|
return true;
|
|
}
|
|
|
|
private byte[] plainTextReceived;
|
|
import core.sync.mutex : Mutex;
|
|
private Mutex plainTextReceivedLock;
|
|
private void decryptedInputHandler(in ubyte[] receivedDecryptedData)
|
|
{
|
|
// TODO: This is now decrypted and THIS data should be placed
|
|
// ... into a buffer in CryptClient that can be read from
|
|
// ... via `read/readFully`
|
|
|
|
// FIXME: Add a libsnooze wakeup method which will wake up
|
|
// ... sleeper in the RiverStream `read` method
|
|
|
|
/* Add the received bytes */
|
|
plainTextReceivedLock.lock();
|
|
plainTextReceived ~= receivedDecryptedData;
|
|
plainTextReceivedLock.unlock();
|
|
|
|
}
|
|
|
|
private void tlsAlertHandler(in TLSAlert alert, in ubyte[] data)
|
|
{
|
|
|
|
}
|
|
|
|
// NOTE This gets called when the Botan client needs to write to
|
|
// ... the underlying output. So If we were to call `botanClient.send`
|
|
// ... (which takes in our plaintext), it would encrypt, and then
|
|
// ... push the encrypted payload into this method here below
|
|
// ... (implying we should write to our underlying stream here)
|
|
private void tlsOutputHandler(in ubyte[] dataOut)
|
|
{
|
|
stream.writeFully(cast(byte[])dataOut);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Below are all our RiverStream API methods which are
|
|
* just for extracting plaintext data (TLS-decrypted)
|
|
* and pushing in plaintext data (to-be TLS-encrypted)
|
|
*/
|
|
|
|
/**
|
|
* Reads decrypted data from the stream. Will read up
|
|
* to the number of bytes equal to the size of the array.
|
|
*
|
|
* Params:
|
|
* toArray = the data read
|
|
* Returns: the number of bytes read
|
|
*/
|
|
public override ulong read(byte[] toArray)
|
|
{
|
|
/* Ensure the TLS session is active */
|
|
openCheck();
|
|
|
|
// FIXME: Have a wake method here (using libsnooze)
|
|
// ... to know when to wake up when there are enough bytes
|
|
// ... available to satisfy the requested amount
|
|
|
|
plainTextReceivedLock.lock();
|
|
plainTextReceived; // TODO: Chomp here
|
|
plainTextReceivedLock.unlock();
|
|
|
|
// TODO: Implement me
|
|
return 0;
|
|
}
|
|
|
|
public override ulong readFully(byte[] toArray)
|
|
{
|
|
/* Ensure the TLS session is active */
|
|
openCheck();
|
|
|
|
// TODO: It seems we can basically have everything or nothing
|
|
// ... so just call read as the method of TLS records does this
|
|
// ... to us
|
|
return read(toArray);
|
|
}
|
|
|
|
public override ulong write(byte[] fromArray)
|
|
{
|
|
/* Ensure the TLS session is active */
|
|
openCheck();
|
|
|
|
// TODO: Implement me
|
|
|
|
/* Send data to Botan */
|
|
botanClient.send(cast(ubyte*)fromArray.ptr, fromArray.length);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
public override ulong writeFully(byte[] fromArray)
|
|
{
|
|
/* Ensure the TLS session is active */
|
|
openCheck();
|
|
|
|
// TODO: Implement me
|
|
|
|
// FIXME: Change this (if required), for now I am just going to call write
|
|
return write(fromArray);
|
|
|
|
// return 0;
|
|
}
|
|
|
|
public override void close()
|
|
{
|
|
// TODO: Implement me
|
|
}
|
|
|
|
/**
|
|
* Ensures that the TLS client is active is open, if not, then throws an
|
|
* exception
|
|
*/
|
|
private void openCheck()
|
|
{
|
|
if(!botanClient.isActive())
|
|
{
|
|
throw new StreamException(StreamError.CLOSED);
|
|
}
|
|
}
|
|
}
|
|
|
|
version(unittest)
|
|
{
|
|
import river.core;
|
|
import river.impls : SockStream;
|
|
import std.socket;
|
|
import std.stdio;
|
|
|
|
import cryptstream.streams.server;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
/**
|
|
* Setup a server
|
|
*/
|
|
// Address addr = parseAddress("::1", 1042);
|
|
// writeln("Binding server to: ", addr);
|
|
// Server server = new Server(addr);
|
|
// server.start();
|
|
|
|
Address addr = getAddress("deavmi.assigned.network", 443)[0];
|
|
|
|
|
|
Socket endpoint = new Socket(AddressFamily.INET6, SocketType.STREAM, ProtocolType.TCP);
|
|
|
|
|
|
endpoint.connect(addr);
|
|
|
|
Stream stream = new SockStream(endpoint);
|
|
CryptClient client = new CryptClient(stream);
|
|
|
|
Thread.sleep(dur!("seconds")(3));
|
|
|
|
// FIXME: This crashes as handshake doens't go through meaning the `isActive()` is false
|
|
client.writeFully(cast(byte[])"ABBA");
|
|
|
|
|
|
while(true){}
|
|
} |