-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
branca: Cleanup implementation and add some error checking.
- Loading branch information
Showing
4 changed files
with
172 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
use std::{fmt, result}; | ||
use std::error::Error as StdErr; | ||
|
||
|
||
#[derive(Debug, Eq, PartialEq, Copy, Clone)] | ||
pub enum Error { | ||
InvalidBase62Token, | ||
InvalidTokenVersion, | ||
BadNonceLength, | ||
BadKeyLength, | ||
ExpiredToken, | ||
DecryptFailed, | ||
SodiumInitFailed, | ||
} | ||
|
||
impl fmt::Display for Error { | ||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||
write!(fmt, "{}", self.description()) | ||
} | ||
} | ||
|
||
impl StdErr for Error { | ||
fn description(&self) -> &str { | ||
match *self { | ||
Error::InvalidBase62Token => "Base62 token is invalid.", | ||
Error::InvalidTokenVersion => "Token version is invalid.", | ||
Error::BadNonceLength => "Bad nonce length.", | ||
Error::BadKeyLength => "Bad key length.", | ||
Error::ExpiredToken => "This token has expired.", | ||
Error::SodiumInitFailed => "Libsodium initialisation failed.", | ||
Error::DecryptFailed => "Decryption failed." | ||
} | ||
} | ||
} | ||
|
||
pub type Result<T> = result::Result<T, Error>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,122 +1,190 @@ | ||
extern crate sodiumoxide; | ||
extern crate serde; | ||
extern crate sodiumoxide_xchacha20poly1305 as sodiumoxide; | ||
extern crate byteorder; | ||
extern crate chrono; | ||
extern crate base_x; | ||
extern crate chrono; | ||
|
||
pub mod errors; | ||
|
||
use byteorder::*; | ||
use base_x::{encode as b62_encode, decode as b62_decode}; | ||
use chrono::prelude::*; | ||
use base_x::*; | ||
|
||
use sodiumoxide::randombytes::*; | ||
use sodiumoxide::crypto::aead::xchacha20poly1305_ietf; | ||
use std::io::*; | ||
use std::io::Read; | ||
use errors::Error as BrancaError; | ||
use sodiumoxide::crypto::aead::xchacha20poly1305_ietf as xchacha20; | ||
|
||
// Branca magic byte | ||
// Branca magic byte. | ||
const VERSION: u8 = 0xBA; | ||
// Branca nonce bytes | ||
// Branca nonce bytes. | ||
const NONCE_BYTES: usize = 24; | ||
// Base 62 alphabet | ||
// Base 62 alphabet. | ||
const BASE62: &'static str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; | ||
|
||
// Branca builder | ||
#[derive(Clone, PartialEq, Debug)] | ||
pub struct Branca { | ||
key: Option<Vec<u8>>, | ||
nonce: Option<Vec<u8>>, | ||
message:Option<String>, | ||
ttl: Option<u32>, | ||
timestamp: Option<u32>, | ||
key: Vec<u8>, | ||
nonce: Vec<u8>, | ||
ttl: u32, | ||
timestamp: u32, | ||
} | ||
|
||
impl Branca { | ||
pub fn new () -> Branca { | ||
Branca { | ||
key: None, | ||
nonce: None, | ||
message: None, | ||
ttl: None, | ||
timestamp: None | ||
key: Default::default(), | ||
nonce: Default::default(), | ||
ttl: Default::default(), | ||
timestamp: Default::default() | ||
} | ||
} | ||
pub fn key(&self) -> &Vec<u8> { | ||
&self.key | ||
} | ||
|
||
pub fn set_key(mut self, key: Vec<u8>) -> Self { | ||
self.key = Some(key); | ||
self | ||
pub fn nonce(&self) -> &Vec<u8> { | ||
&self.nonce | ||
} | ||
|
||
pub fn set_nonce(mut self, nonce:Vec<u8> ) -> Self { | ||
self.nonce = Some(nonce); | ||
pub fn ttl(&self) -> u32 { | ||
self.ttl | ||
} | ||
|
||
pub fn timestamp(&self) -> u32 { | ||
self.timestamp | ||
} | ||
|
||
pub fn set_key(mut self, key: Vec<u8>) -> Self { | ||
self.key = key; | ||
self | ||
} | ||
|
||
pub fn set_message(mut self, message:String) -> Self { | ||
self.message = Some(message); | ||
pub fn set_nonce(mut self, nonce:Vec<u8> ) -> Self { | ||
self.nonce = nonce; | ||
self | ||
} | ||
|
||
pub fn set_ttl(mut self, ttl: u32) -> Self { | ||
self.ttl = Some(ttl); | ||
self.ttl = ttl; | ||
self | ||
} | ||
|
||
pub fn set_timestamp(mut self, timestamp: u32) -> Self { | ||
self.timestamp = Some(timestamp); | ||
self.timestamp = timestamp; | ||
self | ||
} | ||
|
||
pub fn build(self) -> Result<String> { | ||
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(); | ||
pub fn build(self, message: String) -> Result<String, BrancaError> { | ||
let key = self.key; | ||
let nonce = self.nonce; | ||
let mut timestamp = self.timestamp; | ||
|
||
if timestamp <= 0 { | ||
let duration = Utc::now(); | ||
timestamp = duration.timestamp() as u32; | ||
} | ||
|
||
let crypted = encode(message, key, nonce, timestamp); | ||
return Ok(crypted.unwrap()); | ||
} | ||
} | ||
|
||
pub fn encode(msg: String, key: Vec<u8>, nonce: Vec<u8>, timestamp: u32) -> Result<String> { | ||
pub fn encode(msg: String, key: Vec<u8>, nonce: Vec<u8>, timestamp: u32) -> Result<String, BrancaError> { | ||
|
||
sodiumoxide::init(); | ||
// Initialise sodiumoxide before doing anything else. | ||
sodiumoxide::init().map_err(|_e| BrancaError::SodiumInitFailed).ok(); | ||
|
||
let k = xchacha20poly1305_ietf::Key::from_slice(key.as_slice()).unwrap(); | ||
// Check the nonce length before going any further. | ||
|
||
let mut nonce_n: [u8; 24] = Default::default(); | ||
nonce_n.copy_from_slice(nonce.as_slice()); | ||
let nonce_b = xchacha20poly1305_ietf::Nonce(nonce_n); | ||
if nonce.len() != 24 { | ||
return Err(BrancaError::BadNonceLength); | ||
} | ||
|
||
let timestamp: u32 = timestamp; | ||
// Check the key length before going any further. | ||
|
||
if key.len() != 32 { | ||
return Err(BrancaError::BadKeyLength); | ||
} | ||
|
||
// We now can create a Key and Nonce struct from the inputs. | ||
let k = xchacha20::Key::from_slice(key.as_slice()).unwrap(); | ||
|
||
let mut nonce_n = [0u8; NONCE_BYTES]; | ||
nonce_n.copy_from_slice(nonce.as_slice()); | ||
let nonce_b = xchacha20::Nonce(nonce_n); | ||
|
||
// The nonce is now appended to the timestamp in a Vector. | ||
let mut time_bytes = vec![0x0; 4]; | ||
BigEndian::write_u32(&mut time_bytes, timestamp); | ||
time_bytes.append(&mut Vec::from(nonce)); | ||
|
||
// We append the version header to the timestamp vector. | ||
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); | ||
// Encrypt the payload using XChaCha20-Poly1305 AEAD // | ||
let mut crypted = xchacha20::seal(msg.as_bytes(), Some(version_header.as_slice()), &nonce_b, &k); | ||
|
||
// The ciphertext is appended to the version header | ||
version_header.append(&mut crypted); | ||
|
||
// Our payload is now encoded into base62. | ||
let b62_enc = b62_encode(BASE62, &mut version_header.as_slice()); | ||
|
||
let b62_enc = base_x::encode(BASE62, &mut version_header.as_slice()); | ||
|
||
// Return the branca token as a string. | ||
return Ok(b62_enc.to_string()); | ||
} | ||
|
||
pub fn decode(data: String, key: String) -> Result<String> { | ||
let g_data = base_x::decode(BASE62, &data).unwrap(); | ||
let k = xchacha20poly1305_ietf::Key::from_slice(key.as_bytes()).unwrap(); | ||
pub fn decode(data: String, key: String, ttl: u32) -> Result<String, BrancaError> { | ||
|
||
// The key must be 32 bits in size. | ||
if key.len() != 32 { | ||
return Err(BrancaError::BadKeyLength); | ||
} | ||
|
||
|
||
let decoded_data = b62_decode(BASE62, &data).expect("Base62 token is invalid."); | ||
|
||
// Obtain supplied key | ||
let key = xchacha20::Key::from_slice(key.as_bytes()).unwrap(); | ||
|
||
let header = &g_data[0..29]; | ||
let ciphertext = &g_data[29..]; | ||
// After we have decoded the data, the branca token is now represented | ||
// by the following layout below: | ||
|
||
let mut nonce_n: [u8; 24] = Default::default(); | ||
// Branca( header[0..29] + ciphertext[29..] ) | ||
// Version (&u8) || Timestamp (u32) || Nonce ([u8;24]) || Ciphertext (&[u8]) || Tag ([u8:16]) | ||
|
||
// We then obtain the header, ciphertext, version and timestamp with this layout. | ||
let header = &decoded_data[0..29]; | ||
let ciphertext = &decoded_data[29..]; | ||
let version = &header[0]; | ||
let timestamp = BigEndian::read_u32(&header[1..5]); | ||
|
||
// Obtain the nonce from the header // | ||
let mut nonce_n = [0u8; NONCE_BYTES]; | ||
nonce_n.copy_from_slice(&header[5..]); | ||
let nonce_b = xchacha20poly1305_ietf::Nonce(nonce_n); | ||
let nonce_b = xchacha20::Nonce(nonce_n); | ||
|
||
// Check the version | ||
if version != &VERSION { | ||
return Err(BrancaError::InvalidTokenVersion); | ||
} | ||
|
||
// Retrieve plaintext using XChaCha20-Poly1305 AEAD | ||
let decrypted_plaintext = xchacha20::open(ciphertext, Some(header), &nonce_b, &key); | ||
|
||
let decode = xchacha20poly1305_ietf::open(ciphertext, Some(header), &nonce_b, &k).unwrap(); | ||
if !decrypted_plaintext.is_ok() { | ||
return Err(BrancaError::DecryptFailed); | ||
} | ||
|
||
// Timestamp check for expried token // | ||
if ttl != 0 { | ||
let future = timestamp + ttl; | ||
let now = Utc::now().timestamp() as u32; | ||
if future < now { | ||
return Err(BrancaError::ExpiredToken); | ||
} | ||
} | ||
|
||
return Ok(String::from_utf8(decode).unwrap()); | ||
// Return the decoded string // | ||
return Ok(String::from_utf8(decrypted_plaintext.unwrap()).unwrap()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters