Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace libsodium with pure-Rust alternatives #85

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
Cargo.lock
*~
*.swp
.idea/
#*#
13 changes: 8 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "macaroon"
version = "0.3.1-dev.0"
version = "0.4.0-dev.0"
edition = "2021"
rust-version = "1.56"
rust-version = "1.62"
authors = ["Jack Lund <[email protected]>", "macaroon-rs Contributors"]
description = "Fully functional implementation of macaroons in Rust"
documentation = "https://docs.rs/macaroon"
Expand All @@ -15,9 +15,12 @@ license = "MIT"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sodiumoxide = "0.2"
base64 = "0.13"
chacha20poly1305 = "0.10"
sha2 = "0.10"
hmac = "0.12"
rand = "0.8"
base64 = "0.20"

[dev-dependencies]
env_logger = "0.9"
env_logger = "0.10"
time = { version = "0.3", features = ["parsing"] }
11 changes: 6 additions & 5 deletions src/caveat.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::fmt::Debug;

use crate::ByteString;
use crate::crypto;
use crate::crypto::key::MacaroonKey;
use crate::error::MacaroonError;
use crate::ByteString;
use crate::Result;
use crypto::MacaroonKey;
use std::fmt::Debug;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Caveat {
Expand Down Expand Up @@ -44,8 +45,8 @@ impl ThirdParty {
impl Caveat {
pub fn sign(&self, key: &MacaroonKey) -> MacaroonKey {
match self {
Self::FirstParty(fp) => crypto::hmac(key, &fp.predicate),
Self::ThirdParty(tp) => crypto::hmac2(key, &tp.verifier_id, &tp.id),
Self::FirstParty(fp) => crypto::key::hmac(key, &fp.predicate),
Self::ThirdParty(tp) => crypto::key::hmac2(key, &tp.verifier_id, &tp.id),
}
}
}
Expand Down
128 changes: 51 additions & 77 deletions src/crypto.rs → src/crypto/key.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use crate::error::MacaroonError;
use crate::Result;
use sodiumoxide::crypto::auth::hmacsha256::{authenticate, gen_key, Key, Tag};
use sodiumoxide::crypto::secretbox;
use std::borrow::Borrow;
use std::ops::{Deref, DerefMut};

use chacha20poly1305::aead::rand_core::RngCore;
use hmac::Mac;

use crate::crypto::{Decryptor, DefaultEncryptor, Encryptor, MacaroonHmac};

pub const NONCE_BYTES: usize = 12usize;
pub const KEY_BYTES: usize = 32usize;

const KEY_GENERATOR: MacaroonKey = MacaroonKey(*b"macaroons-key-generator\0\0\0\0\0\0\0\0\0");

/// Secret cryptographic key used to sign and verify Macaroons.
Expand All @@ -24,7 +28,6 @@ const KEY_GENERATOR: MacaroonKey = MacaroonKey(*b"macaroons-key-generator\0\0\0\
/// #
/// # fn main() -> Result<(), Box<dyn Error>> {
/// use macaroon::MacaroonKey;
/// extern crate base64;
///
/// // generate a new random key from scratch
/// let fresh_key = MacaroonKey::generate_random();
Expand All @@ -40,10 +43,10 @@ const KEY_GENERATOR: MacaroonKey = MacaroonKey(*b"macaroons-key-generator\0\0\0\
/// # }
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MacaroonKey([u8; sodiumoxide::crypto::auth::KEYBYTES]);
pub struct MacaroonKey(pub [u8; KEY_BYTES]);

impl AsRef<[u8; sodiumoxide::crypto::auth::KEYBYTES]> for MacaroonKey {
fn as_ref(&self) -> &[u8; sodiumoxide::crypto::auth::KEYBYTES] {
impl AsRef<[u8; KEY_BYTES]> for MacaroonKey {
fn as_ref(&self) -> &[u8; KEY_BYTES] {
&self.0
}
}
Expand All @@ -54,8 +57,8 @@ impl AsRef<[u8]> for MacaroonKey {
}
}

impl Borrow<[u8; sodiumoxide::crypto::auth::KEYBYTES]> for MacaroonKey {
fn borrow(&self) -> &[u8; sodiumoxide::crypto::auth::KEYBYTES] {
impl Borrow<[u8; KEY_BYTES]> for MacaroonKey {
fn borrow(&self) -> &[u8; KEY_BYTES] {
&self.0
}
}
Expand All @@ -74,26 +77,37 @@ impl DerefMut for MacaroonKey {
}
}

impl From<Key> for MacaroonKey {
fn from(k: Key) -> Self {
MacaroonKey(k.0)
}
}

impl From<[u8; sodiumoxide::crypto::auth::KEYBYTES]> for MacaroonKey {
impl From<[u8; KEY_BYTES]> for MacaroonKey {
/// Uses bytes directly as a MacaroonKey (with no HMAC)
fn from(b: [u8; sodiumoxide::crypto::auth::KEYBYTES]) -> Self {
fn from(b: [u8; KEY_BYTES]) -> Self {
MacaroonKey(b)
}
}

impl From<&[u8; sodiumoxide::crypto::auth::KEYBYTES]> for MacaroonKey {
impl From<&[u8; KEY_BYTES]> for MacaroonKey {
/// Uses bytes directly as a MacaroonKey (with no HMAC)
fn from(b: &[u8; sodiumoxide::crypto::auth::KEYBYTES]) -> Self {
fn from(b: &[u8; KEY_BYTES]) -> Self {
MacaroonKey(*b)
}
}

impl From<Vec<u8>> for MacaroonKey {
fn from(bytes: Vec<u8>) -> Self {
if bytes.len() < KEY_BYTES {
panic!("invalid key size {} != {}", bytes.len(), KEY_BYTES)
}

let mut ret: [u8; KEY_BYTES] = [0; KEY_BYTES];
for (i, b) in bytes.iter().enumerate() {
if i == KEY_BYTES {
break;
}
ret[i] = *b;
}
MacaroonKey(ret)
}
}

impl MacaroonKey {
/// Generate a new random key, using a secure random number generator.
///
Expand All @@ -102,7 +116,11 @@ impl MacaroonKey {
/// let key = MacaroonKey::generate_random();
/// ```
pub fn generate_random() -> Self {
MacaroonKey(gen_key().0)
let mut rng = rand::thread_rng();
let mut key: [u8; KEY_BYTES] = [0; KEY_BYTES];
rng.fill_bytes(&mut key);
MacaroonKey(key)
// MacaroonKey(MacaroonHmac::generate_key(rng).into())
}

/// Use some seed data to reproducibly generate a MacaroonKey via HMAC.
Expand All @@ -126,16 +144,19 @@ fn generate_derived_key(key: &[u8]) -> MacaroonKey {

pub fn hmac<T, U>(key: &T, text: &U) -> MacaroonKey
where
T: AsRef<[u8; sodiumoxide::crypto::auth::KEYBYTES]> + ?Sized,
T: AsRef<[u8; KEY_BYTES]> + ?Sized,
U: AsRef<[u8]> + ?Sized,
{
let Tag(result_bytes) = authenticate(text.as_ref(), &Key(*key.as_ref()));
MacaroonKey(result_bytes)
let mut mac = <MacaroonHmac as Mac>::new_from_slice(key.as_ref())
.expect("could not create Hmac");
mac.update(text.as_ref());
let bytes = mac.finalize().into_bytes().to_vec();
bytes.into()
}

pub fn hmac2<T, U>(key: &T, text1: &U, text2: &U) -> MacaroonKey
where
T: AsRef<[u8; sodiumoxide::crypto::auth::KEYBYTES]> + ?Sized,
T: AsRef<[u8; KEY_BYTES]> + ?Sized,
U: AsRef<[u8]> + ?Sized,
{
let MacaroonKey(tmp1) = hmac(key, text1);
Expand All @@ -146,62 +167,15 @@ where

pub fn encrypt_key<T>(key: &T, plaintext: &T) -> Vec<u8>
where
T: AsRef<[u8; sodiumoxide::crypto::auth::KEYBYTES]> + ?Sized,
T: AsRef<[u8; KEY_BYTES]> + ?Sized
{
let nonce = secretbox::gen_nonce();
let encrypted = secretbox::seal(plaintext.as_ref(), &nonce, &secretbox::Key(*key.as_ref()));
let mut ret: Vec<u8> = Vec::new();
ret.extend(&nonce.0);
ret.extend(encrypted);
ret
DefaultEncryptor::encrypt(key, plaintext.as_ref()).unwrap()
}

pub fn decrypt_key<T, U>(key: &T, data: &U) -> Result<MacaroonKey>
pub fn decrypt_key<T, U>(key: &T, data: &U) -> crate::Result<MacaroonKey>
where
T: AsRef<[u8; sodiumoxide::crypto::auth::KEYBYTES]> + ?Sized,
T: AsRef<[u8; KEY_BYTES]> + ?Sized,
U: AsRef<[u8]> + ?Sized,
{
let raw_data: &[u8] = data.as_ref();
if raw_data.len() <= secretbox::NONCEBYTES + secretbox::MACBYTES {
error!("crypto::decrypt: Encrypted data {:?} too short", raw_data);
return Err(MacaroonError::CryptoError("encrypted data too short"));
}
let mut nonce: [u8; secretbox::NONCEBYTES] = [0; secretbox::NONCEBYTES];
nonce.clone_from_slice(&raw_data[..secretbox::NONCEBYTES]);
let mut temp: Vec<u8> = Vec::new();
temp.extend(&raw_data[secretbox::NONCEBYTES..]);
let ciphertext = temp.as_slice();
match secretbox::open(
ciphertext,
&secretbox::Nonce(nonce),
&secretbox::Key(*key.as_ref()),
) {
Ok(plaintext) => Ok(Key::from_slice(&plaintext)
.ok_or(MacaroonError::CryptoError(
"supplied key has wrong length (expected 32 bytes)",
))?
.into()),
Err(()) => {
error!(
"crypto::decrypt: Unknown decryption error decrypting {:?}",
raw_data
);
Err(MacaroonError::CryptoError("failed to decrypt ciphertext"))
}
}
}

#[cfg(test)]
mod test {
use super::{decrypt_key, encrypt_key, MacaroonKey};

#[test]
fn test_encrypt_decrypt() {
// NOTE: these are keys as byte sequences, not generated via HMAC
let secret: MacaroonKey = b"This is my encrypted key\0\0\0\0\0\0\0\0".into();
let key: MacaroonKey = b"This is my secret key\0\0\0\0\0\0\0\0\0\0\0".into();
let encrypted = encrypt_key(&key, &secret);
let decrypted = decrypt_key(&key, &encrypted).unwrap();
assert_eq!(secret, decrypted);
}
DefaultEncryptor::decrypt(key, data.as_ref())
}
101 changes: 101 additions & 0 deletions src/crypto/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use chacha20poly1305::{ChaCha20Poly1305, Key, KeyInit, Nonce};
use chacha20poly1305::aead::Aead;
use hmac::Hmac;
use rand::RngCore;
use sha2::Sha256;

use crate::crypto::key::*;
use crate::error::MacaroonError;
use crate::Result;

pub mod key;

pub type MacaroonHmac = Hmac<Sha256>;

pub trait Encryptor<T>
where
T: AsRef<[u8; KEY_BYTES]> + ?Sized,
{
fn encrypt(with_key: &T, clear_bytes: &[u8]) -> Result<Vec<u8>>;
}

pub trait Decryptor<T>
where
T: AsRef<[u8; KEY_BYTES]> + ?Sized,
{
fn decrypt(with_key: &T, cipher_bytes: &[u8]) -> Result<MacaroonKey>;
}


/// The default implementation of an Encryptor and Decryptor.
/// Uses Chacha20-Poly1305 AEAD
pub struct DefaultEncryptor<T: ?Sized> {
_phantom: std::marker::PhantomData<T>,
}

impl<T> Encryptor<T> for DefaultEncryptor<T>
where
T: AsRef<[u8; KEY_BYTES]> + ?Sized,
{
fn encrypt(with_key: &T, clear_bytes: &[u8]) -> Result<Vec<u8>> {
let mut rng = rand::thread_rng();
let mut nonce_bytes: [u8; NONCE_BYTES] = [0; NONCE_BYTES];
rng.fill_bytes(&mut nonce_bytes);

let key = Key::from_slice(with_key.as_ref());
let cipher = ChaCha20Poly1305::new(&key);
let nonce = Nonce::from(nonce_bytes);

let encrypted = cipher.encrypt(&nonce, clear_bytes)
.expect("encrypt_macaroon_key: could not encrypt");

let mut ret: Vec<u8> = Vec::new();
ret.extend(nonce_bytes);
ret.extend(encrypted);

Ok(ret)
}
}

impl<T> Decryptor<T> for DefaultEncryptor<T>
where
T: AsRef<[u8; KEY_BYTES]> + ?Sized,
{
fn decrypt(with_key: &T, cipher_bytes: &[u8]) -> Result<MacaroonKey> {
let raw_data: &[u8] = cipher_bytes.as_ref();
if raw_data.len() <= NONCE_BYTES + KEY_BYTES {
println!("crypto::decrypt: Encrypted data too short ({})", raw_data.len());
return Err(MacaroonError::CryptoError("Encrypted data too short"));
}

let mut nonce_bytes: [u8; NONCE_BYTES] = [0; NONCE_BYTES];
nonce_bytes.clone_from_slice(&raw_data[..NONCE_BYTES]);

let mut sealed: Vec<u8> = Vec::new();
sealed.extend(&raw_data[NONCE_BYTES..]);

let key = Key::from_slice(with_key.as_ref());
let cipher = ChaCha20Poly1305::new(&key);
let nonce = Nonce::from(nonce_bytes);

let decrypted = cipher.decrypt(&nonce, sealed.as_ref())
.expect("decrypt_macaroon_key: could not decrypt");

Ok(decrypted.into())
}
}

#[cfg(test)]
mod test {
use super::{Decryptor, DefaultEncryptor, Encryptor, MacaroonKey};

#[test]
fn test_encrypt_decrypt() {
// NOTE: these are keys as byte sequences, not generated via HMAC
let secret: MacaroonKey = b"This is my encrypted key\0\0\0\0\0\0\0\0".into();
let key: MacaroonKey = b"This is my secret key\0\0\0\0\0\0\0\0\0\0\0".into();
let encrypted = DefaultEncryptor::encrypt(&key, secret.as_ref()).unwrap();
let decrypted = DefaultEncryptor::decrypt(&key, encrypted.as_ref()).unwrap();
assert_eq!(secret, decrypted);
}
}
Loading