Skip to content
Draft
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
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ serde = { version = "1.0.219", features = ["derive"] }
log = "0.4.27"
env_logger = "0.10"

[dev-dependencies]
hex = "0.4"

[features]
default = ["alloc"]
alloc = []
fernet-aes128 = []
python-interop = ["default"]

[build-dependencies]
tonic-build = "0.13.0"
Expand Down
47 changes: 30 additions & 17 deletions src/crypt/fernet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type HmacSha256 = Hmac<Sha256>;
const HMAC_OUT_SIZE: usize = <<HmacSha256 as OutputSizeUser>::OutputSize as Unsigned>::USIZE;
const AES_KEY_SIZE: usize = <<AesAlgo as KeySizeUser>::KeySize as Unsigned>::USIZE;
const IV_KEY_SIZE: usize = <<AesCbcEnc as IvSizeUser>::IvSize as Unsigned>::USIZE;
// Token layout: IV (IV_KEY_SIZE bytes) || ciphertext || HMAC (HMAC_OUT_SIZE bytes)
const FERNET_OVERHEAD_SIZE: usize = IV_KEY_SIZE + HMAC_OUT_SIZE;

pub struct PlainText<'a>(&'a [u8]);
Expand Down Expand Up @@ -161,28 +162,17 @@ impl<R: CryptoRngCore + Copy> Fernet<R> {
return Err(RnsError::InvalidArgument);
}

let expected_tag = &token_data[token_data.len() - HMAC_OUT_SIZE..];
let (data, expected_tag) = token_data.split_at(token_data.len() - HMAC_OUT_SIZE);

let mut hmac = <HmacSha256 as Mac>::new_from_slice(&self.sign_key)
.map_err(|_| RnsError::InvalidArgument)?;

hmac.update(&token_data[..token_data.len() - HMAC_OUT_SIZE]);
hmac.update(data);

let actual_tag = hmac.finalize().into_bytes();
hmac.verify_slice(expected_tag)
.map_err(|_| RnsError::IncorrectSignature)?;

let valid = expected_tag
.iter()
.zip(actual_tag.as_slice())
.map(|(x, y)| x.cmp(y))
.find(|&ord| ord != cmp::Ordering::Equal)
.unwrap_or(actual_tag.len().cmp(&expected_tag.len()))
== cmp::Ordering::Equal;

if valid {
Ok(VerifiedToken { 0: token_data })
} else {
Err(RnsError::IncorrectSignature)
}
Ok(VerifiedToken { 0: token_data })
}

pub fn decrypt<'a, 'b>(
Expand Down Expand Up @@ -212,7 +202,8 @@ impl<R: CryptoRngCore + Copy> Fernet<R> {

#[cfg(test)]
mod tests {
use crate::crypt::fernet::Fernet;
use super::{Fernet, Token, IV_KEY_SIZE};
use crate::error::RnsError;
use core::str;
use rand_core::OsRng;

Expand Down Expand Up @@ -248,4 +239,26 @@ mod tests {
let mut out_buf = [0u8; 12];
assert!(fernet.encrypt(test_msg.into(), &mut out_buf[..]).is_err());
}

#[test]
fn tamper_detection_fails_verification() {
const BUF_SIZE: usize = 4096;
let fernet = Fernet::new_rand(OsRng);
let out_msg: &str = "#FERNET_TEST_MESSAGE#";

let mut out_buf = [0u8; BUF_SIZE];
let token = fernet
.encrypt(out_msg.into(), &mut out_buf[..])
.expect("cipher token");

let tamper_indices = [0usize, IV_KEY_SIZE, token.len() - 1];

for idx in tamper_indices {
let mut tampered = token.as_bytes().to_vec();
tampered[idx] ^= 0xFF;

let verification = fernet.verify(Token::from(tampered.as_slice()));
assert!(matches!(verification, Err(RnsError::IncorrectSignature)));
}
}
}
37 changes: 30 additions & 7 deletions src/destination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
hash::{AddressHash, Hash},
identity::{EmptyIdentity, HashIdentity, Identity, PrivateIdentity, PUBLIC_KEY_LENGTH},
packet::{
self, DestinationType, Header, HeaderType, IfacFlag, Packet, PacketContext,
self, ContextFlag, DestinationType, Header, HeaderType, IfacFlag, Packet, PacketContext,
PacketDataBuffer, PacketType, PropagationType,
},
};
Expand Down Expand Up @@ -60,6 +60,7 @@ pub const NAME_HASH_LENGTH: usize = 10;
pub const RAND_HASH_LENGTH: usize = 10;
pub const MIN_ANNOUNCE_DATA_LENGTH: usize =
PUBLIC_KEY_LENGTH * 2 + NAME_HASH_LENGTH + RAND_HASH_LENGTH + SIGNATURE_LENGTH;
pub const RATCHET_PUBLIC_KEY_LENGTH: usize = 32;

#[derive(Copy, Clone)]
pub struct DestinationName {
Expand Down Expand Up @@ -118,8 +119,15 @@ impl DestinationAnnounce {
}

let announce_data = packet.data.as_slice();

if announce_data.len() < MIN_ANNOUNCE_DATA_LENGTH {
let has_ratchet = packet.header.context_flag.is_set();
let min_length = MIN_ANNOUNCE_DATA_LENGTH
+ if has_ratchet {
RATCHET_PUBLIC_KEY_LENGTH
} else {
0
};

if announce_data.len() < min_length {
return Err(RnsError::OutOfMemory);
}

Expand All @@ -146,6 +154,13 @@ impl DestinationAnnounce {
offset += NAME_HASH_LENGTH;
let rand_hash = &announce_data[offset..(offset + RAND_HASH_LENGTH)];
offset += RAND_HASH_LENGTH;
let ratchet = if has_ratchet {
let slice = &announce_data[offset..(offset + RATCHET_PUBLIC_KEY_LENGTH)];
offset += RATCHET_PUBLIC_KEY_LENGTH;
Some(slice)
} else {
None
};
let signature = &announce_data[offset..(offset + SIGNATURE_LENGTH)];
offset += SIGNATURE_LENGTH;
let app_data = &announce_data[offset..];
Expand All @@ -154,14 +169,21 @@ impl DestinationAnnounce {

// Keeping signed data on stack is only option for now.
// Verification function doesn't support prehashed message.
let signed_data = PacketDataBuffer::new()
let mut signed_data = PacketDataBuffer::new();
signed_data
.chain_write(destination.as_slice())?
.chain_write(public_key.as_bytes())?
.chain_write(verifying_key.as_bytes())?
.chain_write(name_hash)?
.chain_write(rand_hash)?
.chain_write(app_data)?
.finalize();
.chain_write(rand_hash)?;

if let Some(ratchet) = ratchet {
signed_data.chain_write(ratchet)?;
}

signed_data.chain_write(app_data)?;

let signed_data = signed_data.finalize();

let signature = Signature::from_slice(signature).map_err(|_| RnsError::CryptoError)?;

Expand Down Expand Up @@ -279,6 +301,7 @@ impl Destination<PrivateIdentity, Input, Single> {
header: Header {
ifac_flag: IfacFlag::Open,
header_type: HeaderType::Type1,
context_flag: ContextFlag::Unset,
propagation_type: PropagationType::Broadcast,
destination_type: DestinationType::Single,
packet_type: PacketType::Announce,
Expand Down
Loading