Posted: October 2023
I have taken quite a break from this project. Recently, I've been spending more and more time on the project.
Before my break, Safenet was in a somewhat working state. Communication over HTTP between client and server was working.
I have overhauled the integrated HTTP server that was previously built-in to the library. Safenet is now platform-agnostic.
What does this mean? The possibilities are endless. One of my long-term goals for this project was the ability for Safenet to be a drop-in replacement for HTTP. I have finally acheived that goal (somewhat).
In order to get there, I had to add support for ring
, a popular cryptographic rust library.
I did not want to obliterate the readability of my code. It's hard enough to read already. So I did the next best thing. Obliterate the ring implementation... haha just kidding. The only right way was hard work. I would have to create a generic API structure for both my original crypto implementation (RustCrypto) and Ring. I created a crate feature for both Rust Crypto and Ring. Depending on which one is enabled, it would compile certain files that would otherwise be ignored. So I wrapped the specific cryptographic functions of each crypto libraries, and created a generic structures that wrap those functions. This will save me time in the future, as if I ever need to add support for another crypto library, I can.
I have prettied of the use of Safenet for other developers. As I said before, safenet is platform-agnostic, so go use your favorite web framework/protocol. I would like to have some sort of standard, so no matter what framework you use it would stay compatible. As of now, I cannot promise it is. The only HTTP endpoint that is mandatory is a "initialize connection" endpoint. It would look something like this:
use safenet::frame::{InitFrame, Frame, DataFrame}; use safenet::uuid::Uuid; // Server // Initialize keypair with client #[post("/conn/init")] fn conn_init(req: Vec<u8>) -> Vec<u8> { // Generates a new init frame, with generated ECDH keys let init_frame = InitFrame::default(); // Returns the server's frame as bytes init_frame.from_peer(&init_frame).unwrap() } // Can now accept DataFrames from previous clients #[post("/echo")] fn echo(req: Vec<u8>) -> Vec<u8> { let mut data_frame = DataFrame::try_from(req.into_boxed_slice()).unwrap(); data_frame.decode_frame(); let client_msg = std::str::from_utf8(&data_frame.body).unwrap(); let new_msg = format!("got: {client_msg}"); let mut res_data_frame = DataFrame::new(new_msg.as_bytes()); res_data_frame.encode_frame(Uuid::from(data_frame.uuid.unwrap())); res_data_frame.to_bytes() }
I have created a chat-group web demo that utilized WebSockets instead of an HTTP API. It is a bit of a hacky implementation, but it works.
The frontend is written in plain HTML and JS, and uses WASM to import the needed Safenet functions. The backend uses warp
, which is responsible for hosting both the frontend and the WebSockets API.
You can compile safenet in WASM projects now, just make sure to disable all default features and enable the "ring" feature.
I have added an options section to all frames. As the name implies, it holds per-peer options. It also holds important metadata about the frame. The only catch, is that it is not encrypted. However, it is not really important. The options are mostly used in connection-negotiation with the peer.
struct Options { // Either InitFrame or DataFrame frame_type: FrameType, // Where to be reached ip_addr: SocketAddr, }
Currently, DataFrames only pass the frame_type
option. This is useful in cases where you do not have routing, like an HTTP API. In my WebSockets demo, I could rely on using WebSockets routing, however
I would need multiple WebSockets open. Instead, the web client only uses one. The server checks for the frame_type
, then acts upon it accordingly.
The connection-negotiation has a couple options. There is the option to send a secondary nonce key which is enabled by default. This is a second ECDH key used for the nonce in ChaCha20Poly1305. This is enabled by default. Safenet will support different encryption types, so there is an encryption_type
option. As of now, it does nothing. When I implement Crystals-KYBER, it will be used.
made by ~~> Mateo