mirror of https://github.com/deavmi/cryptstream
Compare commits
13 Commits
7d45ca806c
...
41f43a4742
Author | SHA1 | Date |
---|---|---|
Tristan B. Velloza Kildaire | 41f43a4742 | |
Tristan B. Velloza Kildaire | 3199a4e394 | |
Tristan B. Velloza Kildaire | 70053118af | |
Tristan B. Velloza Kildaire | 679530662f | |
Tristan B. Velloza Kildaire | d37446cbb5 | |
Tristan B. Velloza Kildaire | 701d1e97b9 | |
Tristan B. Velloza Kildaire | 2c43e0ea53 | |
Tristan B. Velloza Kildaire | 347473df31 | |
Tristan B. Velloza Kildaire | 1b8d11c1af | |
Tristan B. Velloza Kildaire | cf7d29ebbd | |
Tristan B. Velloza Kildaire | 9d2d5155ec | |
Tristan B. Velloza Kildaire | 3b3d513f14 | |
Tristan B. Velloza Kildaire | 0f1221b759 |
|
@ -14,3 +14,5 @@ cryptstream-test-*
|
|||
*.o
|
||||
*.obj
|
||||
*.lst
|
||||
dub.selections.json
|
||||
libcryptstream.a
|
||||
|
|
4
dub.json
4
dub.json
|
@ -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"
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
import std.stdio;
|
||||
|
||||
void main()
|
||||
{
|
||||
writeln("Edit source/app.d to start your project.");
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
module cryptstream;
|
||||
|
||||
public import cryptstream.streams.client;
|
||||
public import cryptstream.streams.server;
|
|
@ -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){}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue