diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 088ba6b..979d87e --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + +/target +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100755 index 0000000..efc11dc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "branca" +description = "A Rust implementation of branca, a JWT alternative." +version = "0.1.0" +authors = ["return"] +keywords = ["fernet", "branca", "crypto", "jwt"] +categories = ["cryptography"] +readme = "README.md" +license = "MIT" +documentation = "https://docs.rs/branca" + +[dependencies] + +base-x = "0.2.3" +byteorder = "1.2.7" +chrono = { version = "0.4.6", features = ["serde"] } +hex = "0.3.2" +# Will change this to point a sodiumoxide release that has crypto_aead_xchacha20poly1305_ietf_* functions. +# In the meantime, use a local version instead. +sodiumoxide = { version = "0.1.0"} +serde = "^1" + +[[example]] +name = "example" +path = "test/main.rs" \ No newline at end of file diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 9ff2e4c..e0b7700 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# branca-rs -A Rust implementation of the branca specification +# branca +A Rust implementation of the branca specification. diff --git a/src/lib.rs b/src/lib.rs new file mode 100755 index 0000000..bac5023 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,122 @@ +extern crate sodiumoxide; +extern crate serde; +extern crate byteorder; +extern crate chrono; +extern crate base_x; + +use byteorder::*; +use chrono::prelude::*; +use base_x::*; + +use sodiumoxide::randombytes::*; +use sodiumoxide::crypto::aead::xchacha20poly1305_ietf; +use std::io::*; +use std::io::Read; + +// Branca magic byte +const VERSION: u8 = 0xBA; +// Branca nonce bytes +const NONCE_BYTES: usize = 24; +// Base 62 alphabet +const BASE62: &'static str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +// Branca builder +#[derive(Clone, PartialEq, Debug)] +pub struct Branca { + key: Option>, + nonce: Option>, + message:Option, + ttl: Option, + timestamp: Option, +} + +impl Branca { + pub fn new () -> Branca { + Branca { + key: None, + nonce: None, + message: None, + ttl: None, + timestamp: None + } + } + + pub fn set_key(mut self, key: Vec) -> Self { + self.key = Some(key); + self + } + + pub fn set_nonce(mut self, nonce:Vec ) -> Self { + self.nonce = Some(nonce); + self + } + + pub fn set_message(mut self, message:String) -> Self { + self.message = Some(message); + self + } + + pub fn set_ttl(mut self, ttl: u32) -> Self { + self.ttl = Some(ttl); + self + } + + pub fn set_timestamp(mut self, timestamp: u32) -> Self { + self.timestamp = Some(timestamp); + self + } + + pub fn build(self) -> Result { + let key = self.key.unwrap(); + let nonce = self.nonce.unwrap(); + let message = self.message.unwrap(); + let ttl = self.ttl.unwrap(); + let timestamp = self.timestamp.unwrap(); + let crypted = encode(message, key, nonce, timestamp); + return Ok(crypted.unwrap()); + } +} + +pub fn encode(msg: String, key: Vec, nonce: Vec, timestamp: u32) -> Result { + + sodiumoxide::init(); + + let k = xchacha20poly1305_ietf::Key::from_slice(key.as_slice()).unwrap(); + + let mut nonce_n: [u8; 24] = Default::default(); + nonce_n.copy_from_slice(nonce.as_slice()); + let nonce_b = xchacha20poly1305_ietf::Nonce(nonce_n); + + let timestamp: u32 = timestamp; + + let mut time_bytes = vec![0x0; 4]; + BigEndian::write_u32(&mut time_bytes, timestamp); + time_bytes.append(&mut Vec::from(nonce)); + + let mut version_header = vec![VERSION]; + version_header.append(&mut time_bytes); + + let mut crypted = xchacha20poly1305_ietf::seal(msg.as_bytes(), Some(version_header.as_slice()), &nonce_b, &k); + + version_header.append(&mut crypted); + + let b62_enc = base_x::encode(BASE62, &mut version_header.as_slice()); + + return Ok(b62_enc.to_string()); +} + +pub fn decode(data: String, key: String) -> Result { + let g_data = base_x::decode(BASE62, &data).unwrap(); + let k = xchacha20poly1305_ietf::Key::from_slice(key.as_bytes()).unwrap(); + + let header = &g_data[0..29]; + let ciphertext = &g_data[29..]; + + let mut nonce_n: [u8; 24] = Default::default(); + nonce_n.copy_from_slice(&header[5..]); + let nonce_b = xchacha20poly1305_ietf::Nonce(nonce_n); + + let decode = xchacha20poly1305_ietf::open(ciphertext, Some(header), &nonce_b, &k).unwrap(); + + return Ok(String::from_utf8(decode).unwrap()); +} \ No newline at end of file diff --git a/test/main.rs b/test/main.rs new file mode 100644 index 0000000..6e64410 --- /dev/null +++ b/test/main.rs @@ -0,0 +1,43 @@ +extern crate branca; + +use branca::{Branca, encode, decode,}; + +fn main(){ +} + +#[cfg(test)] +mod branca_unit_tests { + + use super::*; + use branca::{Branca, encode, decode}; + + #[test] + pub fn test_encode() { + let keygen = String::from("supersecretkeyyoushouldnotcommit").into_bytes(); + let message = String::from("Hello world!"); + let nonce = [0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c].to_vec(); + let timestamp = 123206400; + let branca_token = encode(message,keygen,nonce,timestamp).unwrap(); + assert_eq!(branca_token, "875GH233T7IYrxtgXxlQBYiFobZMQdHAT51vChKsAIYCFxZtL1evV54vYqLyZtQ0ekPHt8kJHQp0a"); + } + + #[test] + pub fn test_decode() { + let ciphertext = String::from("875GH233T7IYrxtgXxlQBYiFobZMQdHAT51vChKsAIYCFxZtL1evV54vYqLyZtQ0ekPHt8kJHQp0a"); + let keygen = String::from("supersecretkeyyoushouldnotcommit"); + assert_eq!(decode(ciphertext, keygen).unwrap(), "Hello world!"); + } + + #[test] + pub fn test_encode_builder() { + let token = Branca::new() + .set_key(String::from("supersecretkeyyoushouldnotcommit").into_bytes()) + .set_message(String::from("Hello world!")) + .set_nonce([0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c].to_vec()) + .set_timestamp(123206400) + .set_ttl(3600) + .build(); + + assert_eq!(token.unwrap(), "875GH233T7IYrxtgXxlQBYiFobZMQdHAT51vChKsAIYCFxZtL1evV54vYqLyZtQ0ekPHt8kJHQp0a"); + } +}