Compare commits

...

13 Commits

Author SHA1 Message Date
Tristan B. Velloza Kildaire 41f43a4742 Client
- Consume received data into a buffer that `read(byte[])` can later consume from
2023-04-29 23:09:42 +02:00
Tristan B. Velloza Kildaire 3199a4e394 - Made a polciy thing to try follow their example as close as possible, big mmmmh moment 2023-04-29 22:37:34 +02:00
Tristan B. Velloza Kildaire 70053118af Credmanager
- Had to make this as was required

Server

- Server-side TLS stream thing

Client

- Client-side stream thing
2023-04-29 22:04:46 +02:00
Tristan B. Velloza Kildaire 679530662f CryptClient
- Added a `Thread` named `streamReader` which is created upon `CryptClient` construction and started. It will read from our river stream `stream` and push the TLS data (bit-by-bit of the record) to the Botan client via `receivedData(ubyte*, sizet)`
2023-04-29 19:14:09 +02:00
Tristan B. Velloza Kildaire d37446cbb5 CryptClient
- `tlsOutputHandler(in ubyte[])` now writes to the underlying `RiverStream`, `stream`
2023-04-29 18:36:31 +02:00
Tristan B. Velloza Kildaire 701d1e97b9 CryptClient
- `writeFully(byte[])` calls `write(byte[])` for now
- `write(byte[])` now calls `botalnClient.send()` with the buffer and its length
2023-04-29 18:31:06 +02:00
Tristan B. Velloza Kildaire 2c43e0ea53 Client
- Call `openCheck()`  remaining methods
2023-04-29 18:25:17 +02:00
Tristan B. Velloza Kildaire 347473df31 Client
- Added `CryptClient` which is a kind-of `Stream` (from the `river` library)
- Constructor takes in an underlying connection, potentially a socket, but some `RiverStream` that can be used for the Botan client to communicate with
- Added `openCheck()` to be called prior to any code inside the `write`, `writeFully`, `read` and `readFully` methods which calls the `botanClient.isActive()`, if this is false (i.e. the TLS session is not active) then an exception (a `StreamException`) is thrown
2023-04-29 18:24:05 +02:00
Tristan B. Velloza Kildaire 1b8d11c1af - Imported botan libraries 2023-04-29 17:43:21 +02:00
Tristan B. Velloza Kildaire cf7d29ebbd - Added package structure 2023-04-29 17:38:00 +02:00
Tristan B. Velloza Kildaire 9d2d5155ec - Set build type to `library`
- Added `botan` as a dependency
2023-04-29 17:37:52 +02:00
Tristan B. Velloza Kildaire 3b3d513f14 - Update `.gitignore` 2023-04-29 17:37:32 +02:00
Tristan B. Velloza Kildaire 0f1221b759 - Update `.gitignore` 2023-04-29 17:37:24 +02:00
8 changed files with 538 additions and 7 deletions

2
.gitignore vendored
View File

@ -14,3 +14,5 @@ cryptstream-test-*
*.o
*.obj
*.lst
dub.selections.json
libcryptstream.a

View File

@ -4,9 +4,11 @@
],
"copyright": "Copyright © 2023, Tristan B. Velloza Kildaire",
"dependencies": {
"botan": "~>1.13.4",
"river": "~>0.3.5"
},
"description": "A minimal D application.",
"license": "LGPL 2",
"name": "cryptstream"
"name": "cryptstream",
"targetType": "library"
}

View File

@ -1,6 +0,0 @@
import std.stdio;
void main()
{
writeln("Edit source/app.d to start your project.");
}

View File

@ -0,0 +1,4 @@
module cryptstream;
public import cryptstream.streams.client;
public import cryptstream.streams.server;

View File

@ -0,0 +1,263 @@
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){}
}

View File

@ -0,0 +1,83 @@
module cryptstream.streams.credmanager;
import botan.tls.client;
import botan.math.bigint.bigint : BigInt;
public class CredManager : TLSCredentialsManager
{
public override Vector!CertificateStore trustedCertificateAuthorities(in string type, in string context)
{
return super.trustedCertificateAuthorities(type, context);
}
public override void verifyCertificateChain(in string type, in string purported_hostname, const ref Vector!X509Certificate cert_chain)
{
return super.verifyCertificateChain(type, purported_hostname, cert_chain);
}
public override Vector!X509Certificate certChain(const ref Vector!string cert_key_types, in string type, in string context)
{
return super.certChain(cert_key_types, type, context);
}
public override Vector!X509Certificate certChainSingleType(in string cert_key_type, in string type, in string context)
{
return super.certChainSingleType(cert_key_type, type, context);
}
public override PrivateKey privateKeyFor(in X509Certificate cert, in string type, in string context)
{
return super.privateKeyFor(cert, type, context);
}
public override bool attemptSrp(in string type, in string context)
{
return super.attemptSrp(type, context);
}
public override string srpIdentifier(in string type, in string context)
{
return super.srpIdentifier(type, context);
}
public override string srpPassword(in string type, in string context, in string identifier)
{
return super.srpPassword(type, context, identifier);
}
public override bool srpVerifier(in string type,
in string context,
in string identifier,
ref string group_name,
ref BigInt verifier,
ref Vector!ubyte salt,
bool generate_fake_on_unknown)
{
return super.srpVerifier(type, context, identifier, group_name, verifier, salt, generate_fake_on_unknown);
}
public override string pskIdentityHint(in string type, in string context)
{
return super.pskIdentityHint(type, context);
}
public override string pskIdentity(in string type, in string context, in string identity_hint)
{
return super.pskIdentity(type, context, identity_hint);
}
public override bool hasPsk()
{
return super.hasPsk();
}
public override PrivateKey channelPrivateKey(string hostname)
{
return super.channelPrivateKey(hostname);
}
public override SymmetricKey psk(in string type, in string context, in string identity)
{
return super.psk(type, context, identity);
}
}

View File

@ -0,0 +1,166 @@
module cryptstream.streams.server;
import botan.tls.server;
import river.core;
import river.impls.sock : SockStream;
import core.thread;
import botan.tls.credentials_manager;
public class CryptServerHandle
{
private Stream clientSocket;
private TLSServer server;
// TODO: Setup
private TLSSessionManager sessionManager = new TLSSessionManagerNoop();
private TLSCredentialsManager credentialManager;
import cryptstream.streams.testingPolicy;
private TLSPolicy policy = new TestingPolicy();
private RandomNumberGenerator rng;
/**
* Reads, in its own thread, bytes from the client
* and pushes them into the Botan server
*/
private Thread streamReader;
this(Stream clientSocket)
{
import std.stdio;
writeln("HOL");
this.rng = RandomNumberGenerator.makeRng();
// this.sessionManager = new TLSSessionManagerInMemory(rng);
import cryptstream.streams.credmanager : CredManager;
this.credentialManager = new CredManager();
this.clientSocket = clientSocket;
version(unittest)
{
import std.stdio;
writeln("CryptServer ctor(): Before TLSServer creation");
}
this.server = new TLSServer(&tlsOutputHandler, &decryptedInputHandler, &tlsAlertHandler, &tlsHandshakeHandler,
sessionManager, credentialManager, policy, rng);
version(unittest)
{
import std.stdio;
writeln("CryptServer ctor(): AFTER TLSServer creation");
}
this.streamReader = new Thread(&streamReaderWorker);
this.streamReader.start();
}
private void streamReaderWorker()
{
// TODO: Make this size configurable
byte[500] readInto;
while(true)
{
ulong receivedAmount = clientSocket.read(readInto);
version(unittest)
{
import std.stdio;
import std.conv : to;
writeln("streamReaderWorker(server-side): Transferring "~to!(string)(receivedAmount)~" many bytes over to Botan server...");
writeln("streamReaderWorker(server-side): The bytes are: "~to!(string)(readInto[0..receivedAmount]));
}
// TODO: Use the hint byte count returned?
server.receivedData(cast(ubyte*)readInto.ptr, receivedAmount);
}
}
private bool tlsHandshakeHandler(in TLSSession session)
{
// TODO: Implement me
import std.stdio;
writeln("tlsHandshakeHandler(server-side) Hello handshake came in?");
return true;
}
private void decryptedInputHandler(in ubyte[] receivedDecryptedData)
{
// TODO: This is now decrypted and THIS data should be placed
// ... into a buffer in CryptServer that can be read from
// ... via `read/readFully`
}
private void tlsAlertHandler(in TLSAlert alert, in ubyte[] data)
{
}
// NOTE This gets called when the Botan server needs to write to
// ... the underlying output. So If we were to call `server.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)
{
clientSocket.writeFully(cast(byte[])dataOut);
}
}
version(unittest)
{
import std.socket;
import cryptstream.streams.client : CryptClient;
import core.thread;
public class Server : Thread
{
private Socket serverSocket;
this(Address bindAddr)
{
serverSocket = new Socket(bindAddr.addressFamily(), SocketType.STREAM, ProtocolType.TCP);
serverSocket.bind(bindAddr);
serverSocket.listen(0);
super(&begin);
}
private void begin()
{
while(true)
{
Socket clientSocket = serverSocket.accept();
Stream clientStream = new SockStream(clientSocket);
CryptServerHandle cryptoClientStream = new CryptServerHandle(clientStream);
Connection clientConnection = new Connection(cryptoClientStream);
clientConnection.start();
}
}
}
public class Connection : Thread
{
private CryptServerHandle client;
this(CryptServerHandle client)
{
this.client = client;
super(&worker);
}
private void worker()
{
while(true)
{
// TODO: Do something here with the client
import std.stdio;
writeln("Hello I am connection");
Thread.sleep(dur!("seconds")(10));
}
}
}
}

View File

@ -0,0 +1,17 @@
module cryptstream.streams.testingPolicy;
import botan.tls.policy : TLSPolicy;
import botan.tls.client : TLSProtocolVersion;
public class TestingPolicy : TLSPolicy
{
public override const bool acceptableProtocolVersion(TLSProtocolVersion _version)
{
return true;
}
public override const bool sendFallbackSCSV(in TLSProtocolVersion _version)
{
return false;
}
}