use crate::{
crypto::{
aes::{cbc, ecb},
chachapoly::{ChaCha, ChaChaPoly},
hmac::Hmac,
sha256::Sha256,
EphemeralPrivateKey, EphemeralPublicKey, StaticPrivateKey, StaticPublicKey,
},
i2np::{tunnel::data::EncryptedTunnelData, HopRole},
runtime::Runtime,
Error,
};
use bytes::Bytes;
use zeroize::Zeroize;
use alloc::{sync::Arc, vec, vec::Vec};
use core::{fmt, mem};
const LOG_TARGET: &str = "emissary::tunnel::noise";
const PROTOCOL_NAME: &str = "Noise_N_25519_ChaChaPoly_SHA256";
pub struct TunnelKeys {
garlic_key: Option<Bytes>,
garlic_tag: Option<Bytes>,
iv_key: Vec<u8>,
layer_key: Vec<u8>,
reply_key: Vec<u8>,
}
impl TunnelKeys {
pub fn garlic_key(&self) -> Bytes {
self.garlic_key.as_ref().expect("garlic key to exist").clone()
}
pub fn garlic_tag(&self) -> Bytes {
self.garlic_tag.as_ref().expect("garlic tag to exist").clone()
}
pub fn iv_key(&self) -> &[u8] {
&self.iv_key
}
pub fn layer_key(&self) -> &[u8] {
&self.layer_key
}
pub fn reply_key(&self) -> &[u8] {
&self.reply_key
}
pub fn decrypt_record<'a>(
&self,
tunnel_data: &'a EncryptedTunnelData<'a>,
) -> (Vec<u8>, Vec<u8>) {
let mut aes = ecb::Aes::new_encryptor(&self.iv_key);
let iv = aes.encrypt(tunnel_data.iv());
let mut aes = cbc::Aes::new_encryptor(&self.layer_key, &iv);
let ciphertext = aes.encrypt(tunnel_data.ciphertext());
let mut aes = ecb::Aes::new_encryptor(&self.iv_key);
let iv = aes.encrypt(iv);
(ciphertext, iv)
}
}
impl TunnelKeys {
fn new(mut chaining_key: Vec<u8>, hop_role: HopRole) -> TunnelKeys {
let mut temp_key = Hmac::new(&chaining_key).update([]).finalize();
let ck = Hmac::new(&temp_key).update(b"SMTunnelReplyKey").update([0x01]).finalize();
let reply_key = Hmac::new(&temp_key)
.update(&ck)
.update(b"SMTunnelReplyKey")
.update([0x02])
.finalize();
temp_key.zeroize();
let mut temp_key = Hmac::new(&ck).update([]).finalize();
let ck = Hmac::new(&temp_key).update(b"SMTunnelLayerKey").update([0x01]).finalize();
let layer_key = Hmac::new(&temp_key)
.update(&ck)
.update(b"SMTunnelLayerKey")
.update([0x02])
.finalize();
match hop_role {
HopRole::InboundGateway | HopRole::Participant => {
temp_key.zeroize();
chaining_key.zeroize();
TunnelKeys {
garlic_key: None,
garlic_tag: None,
iv_key: ck,
layer_key,
reply_key,
}
}
HopRole::OutboundEndpoint => {
let mut temp_key = Hmac::new(&ck).update([]).finalize();
let ck = Hmac::new(&temp_key).update(b"TunnelLayerIVKey").update([0x01]).finalize();
let iv_key = Hmac::new(&temp_key)
.update(&ck)
.update(b"TunnelLayerIVKey")
.update([0x02])
.finalize();
temp_key.zeroize();
let mut temp_key = Hmac::new(&ck).update([]).finalize();
let ck = Hmac::new(&temp_key).update(b"RGarlicKeyAndTag").update([0x01]).finalize();
let garlic_key = Bytes::from(
Hmac::new(&temp_key)
.update(&ck)
.update(b"RGarlicKeyAndTag")
.update([0x02])
.finalize(),
);
let garlic_tag = Bytes::from(ck[..8].to_vec());
temp_key.zeroize();
chaining_key.zeroize();
TunnelKeys {
garlic_key: Some(garlic_key),
garlic_tag: Some(garlic_tag),
iv_key,
layer_key,
reply_key,
}
}
}
}
}
enum ShortInboundSessionState {
Initialized {
chaining_key: Vec<u8>,
aead_key: Vec<u8>,
state: Vec<u8>,
},
RecordDecrypted {
chaining_key: Vec<u8>,
aead_key: Vec<u8>,
state: Vec<u8>,
},
TunnelKeysDerived {
state: Vec<u8>,
tunnel_keys: TunnelKeys,
},
BuildRecordsEncrypted {
tunnel_keys: TunnelKeys,
},
Poisoned,
}
impl fmt::Debug for ShortInboundSessionState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Initialized { .. } =>
f.debug_struct("ShortInboundSessionState::Initialized").finish_non_exhaustive(),
Self::RecordDecrypted { .. } => f
.debug_struct("ShortInboundSessionState::RecordDecrypted")
.finish_non_exhaustive(),
Self::TunnelKeysDerived { .. } => f
.debug_struct("ShortInboundSessionState::TunnelKeysDerived")
.finish_non_exhaustive(),
Self::BuildRecordsEncrypted { .. } => f
.debug_struct("ShortInboundSessionState::BuildRecordsEncrypted")
.finish_non_exhaustive(),
Self::Poisoned => f.debug_struct("ShortInboundSessionState::Poisoned").finish(),
}
}
}
pub struct ShortInboundSession {
state: ShortInboundSessionState,
}
impl ShortInboundSession {
pub fn new(chaining_key: Vec<u8>, aead_key: Vec<u8>, state: Vec<u8>) -> Self {
Self {
state: ShortInboundSessionState::Initialized {
chaining_key,
aead_key,
state,
},
}
}
pub fn decrypt_build_record(&mut self, mut record: Vec<u8>) -> crate::Result<Vec<u8>> {
match mem::replace(&mut self.state, ShortInboundSessionState::Poisoned) {
ShortInboundSessionState::Initialized {
chaining_key,
aead_key,
state,
} => {
let new_state = Sha256::new().update(&state).update(&record).finalize();
ChaChaPoly::new(&aead_key).decrypt_with_ad(&state, &mut record)?;
self.state = ShortInboundSessionState::RecordDecrypted {
state: new_state,
chaining_key,
aead_key,
};
Ok(record)
}
state => {
tracing::warn!(
target: LOG_TARGET,
?state,
"state is poisoned",
);
debug_assert!(false);
Err(Error::InvalidState)
}
}
}
pub fn create_tunnel_keys(&mut self, hop_role: HopRole) -> crate::Result<()> {
match mem::replace(&mut self.state, ShortInboundSessionState::Poisoned) {
ShortInboundSessionState::RecordDecrypted {
chaining_key,
mut aead_key,
state,
} => {
self.state = ShortInboundSessionState::TunnelKeysDerived {
state,
tunnel_keys: TunnelKeys::new(chaining_key, hop_role),
};
aead_key.zeroize();
Ok(())
}
state => {
tracing::warn!(
target: LOG_TARGET,
?state,
"state is poisoned",
);
debug_assert!(false);
Err(Error::InvalidState)
}
}
}
pub fn encrypt_build_records(
&mut self,
payload: &mut [u8],
our_record: usize,
) -> crate::Result<()> {
match mem::replace(&mut self.state, ShortInboundSessionState::Poisoned) {
ShortInboundSessionState::TunnelKeysDerived { state, tunnel_keys } => {
debug_assert!(payload.len() > 218 && (payload.len() - 1).is_multiple_of(218));
payload[1..].chunks_mut(218).enumerate().for_each(|(idx, record)| {
if idx == our_record {
let tag = ChaChaPoly::with_nonce(tunnel_keys.reply_key(), idx as u64)
.encrypt_with_ad(&state, &mut record[0..202])
.unwrap();
record[202..218].copy_from_slice(&tag);
} else {
ChaCha::with_nonce(tunnel_keys.reply_key(), idx as u64).encrypt_ref(record);
}
});
self.state = ShortInboundSessionState::BuildRecordsEncrypted { tunnel_keys };
Ok(())
}
state => {
tracing::warn!(
target: LOG_TARGET,
?state,
"state is poisoned",
);
debug_assert!(false);
Err(Error::InvalidState)
}
}
}
pub fn finalize(self) -> crate::Result<TunnelKeys> {
match self.state {
ShortInboundSessionState::BuildRecordsEncrypted { tunnel_keys } => Ok(tunnel_keys),
state => {
tracing::warn!(
target: LOG_TARGET,
?state,
"state is poisoned",
);
debug_assert!(false);
Err(Error::InvalidState)
}
}
}
}
enum LongInboundSessionState {
Initialized {
chaining_key: Vec<u8>,
aead_key: Vec<u8>,
state: Vec<u8>,
},
RecordDecrypted {
chaining_key: Vec<u8>,
state: Vec<u8>,
},
BuildRecordsEncrypted,
Poisoned,
}
impl fmt::Debug for LongInboundSessionState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Initialized { .. } =>
f.debug_struct("LongInboundSessionState::Initialized").finish_non_exhaustive(),
Self::RecordDecrypted { .. } => f
.debug_struct("LongInboundSessionState::RecordDecrypted")
.finish_non_exhaustive(),
Self::BuildRecordsEncrypted { .. } =>
f.debug_struct("LongInboundSessionState::BuildRecordsEncrypted").finish(),
Self::Poisoned => f.debug_struct("LongInboundSessionState::Poisoned").finish(),
}
}
}
pub struct LongInboundSession {
state: LongInboundSessionState,
}
impl LongInboundSession {
pub fn new(chaining_key: Vec<u8>, aead_key: Vec<u8>, state: Vec<u8>) -> Self {
Self {
state: LongInboundSessionState::Initialized {
chaining_key,
aead_key,
state,
},
}
}
pub fn decrypt_build_record(&mut self, mut record: Vec<u8>) -> crate::Result<Vec<u8>> {
match mem::replace(&mut self.state, LongInboundSessionState::Poisoned) {
LongInboundSessionState::Initialized {
chaining_key,
aead_key,
state,
} => {
let new_state = Sha256::new().update(&state).update(&record).finalize();
ChaChaPoly::new(&aead_key).decrypt_with_ad(&state, &mut record)?;
self.state = LongInboundSessionState::RecordDecrypted {
state: new_state,
chaining_key,
};
Ok(record)
}
state => {
tracing::warn!(
target: LOG_TARGET,
?state,
"state is poisoned",
);
debug_assert!(false);
Err(Error::InvalidState)
}
}
}
pub fn encrypt_build_record(&mut self, record: &mut [u8]) -> crate::Result<()> {
match mem::replace(&mut self.state, LongInboundSessionState::Poisoned) {
LongInboundSessionState::RecordDecrypted {
mut chaining_key,
state,
} => {
let tag = ChaChaPoly::new(&chaining_key)
.encrypt_with_ad(&state, &mut record[0..512])
.unwrap();
record[512..528].copy_from_slice(&tag);
chaining_key.zeroize();
self.state = LongInboundSessionState::BuildRecordsEncrypted;
Ok(())
}
state => {
tracing::warn!(
target: LOG_TARGET,
?state,
"state is poisoned",
);
debug_assert!(false);
Err(Error::InvalidState)
}
}
}
pub fn finalize(self, layer_key: Vec<u8>, iv_key: Vec<u8>) -> crate::Result<TunnelKeys> {
match self.state {
LongInboundSessionState::BuildRecordsEncrypted => Ok(TunnelKeys {
garlic_key: None,
garlic_tag: None,
iv_key,
layer_key,
reply_key: Vec::new(),
}),
state => {
tracing::warn!(
target: LOG_TARGET,
?state,
"state is poisoned",
);
debug_assert!(false);
Err(Error::InvalidState)
}
}
}
}
pub struct OutboundSession {
aead_key: Vec<u8>,
ephemeral_key: Vec<u8>,
state: Vec<u8>,
tunnel_keys: TunnelKeys,
}
impl OutboundSession {
pub fn aead_key(&self) -> &[u8] {
&self.aead_key
}
pub fn ephemeral_key(&self) -> &[u8] {
&self.ephemeral_key
}
pub fn state(&self) -> &[u8] {
&self.state
}
pub fn set_state(&mut self, state: Vec<u8>) {
self.state = state;
}
pub fn garlic_key(&self) -> Bytes {
self.tunnel_keys.garlic_key()
}
pub fn garlic_tag(&self) -> Bytes {
self.tunnel_keys.garlic_tag()
}
pub fn garlic_tag_owned(&self) -> Bytes {
self.tunnel_keys.garlic_tag.clone().expect("garlic tag to exist")
}
pub fn iv_key(&self) -> &[u8] {
self.tunnel_keys.iv_key()
}
pub fn layer_key(&self) -> &[u8] {
self.tunnel_keys.layer_key()
}
pub fn reply_key(&self) -> &[u8] {
self.tunnel_keys.reply_key()
}
}
impl fmt::Debug for OutboundSession {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OutboundSession").finish_non_exhaustive()
}
}
#[derive(Clone)]
pub struct NoiseContext {
chaining_key: Bytes,
inbound_state: Bytes,
outbound_state: Bytes,
local_key: Arc<StaticPrivateKey>,
local_router_hash: Bytes,
}
impl NoiseContext {
pub fn new(local_key: StaticPrivateKey, local_router_hash: Bytes) -> Self {
let chaining_key = {
let mut chaining_key = PROTOCOL_NAME.as_bytes().to_vec();
chaining_key.append(&mut vec![0u8]);
chaining_key
};
let outbound_state = Sha256::new().update(&chaining_key).finalize();
let inbound_state =
Sha256::new().update(&outbound_state).update(local_key.public()).finalize();
Self {
local_router_hash,
chaining_key: Bytes::from(chaining_key),
inbound_state: Bytes::from(inbound_state),
outbound_state: Bytes::from(outbound_state),
local_key: Arc::new(local_key),
}
}
pub fn local_router_hash(&self) -> &Bytes {
&self.local_router_hash
}
pub fn local_public_key(&self) -> StaticPublicKey {
self.local_key.public()
}
pub fn create_outbound_session<R: Runtime>(
&self,
remote_static: StaticPublicKey,
hop_role: HopRole,
) -> OutboundSession {
let local_ephemeral = EphemeralPrivateKey::random(R::rng());
let local_ephemeral_public = local_ephemeral.public().to_vec();
let state = {
let state = Sha256::new()
.update(&self.outbound_state)
.update::<&[u8]>(remote_static.as_ref())
.finalize();
Sha256::new().update(&state).update(&local_ephemeral_public).finalize()
};
let mut shared_secret = local_ephemeral.diffie_hellman(&remote_static);
let mut temp_key = Hmac::new(&self.chaining_key).update(&shared_secret).finalize();
let chaining_key = Hmac::new(&temp_key).update([0x01]).finalize();
let aead_key = Hmac::new(&temp_key).update(&chaining_key).update([0x02]).finalize();
temp_key.zeroize();
shared_secret.zeroize();
OutboundSession {
tunnel_keys: TunnelKeys::new(chaining_key, hop_role),
ephemeral_key: local_ephemeral_public,
state,
aead_key,
}
}
pub fn create_short_inbound_session(
&self,
remote_key: EphemeralPublicKey,
) -> ShortInboundSession {
let mut shared_secret = self.local_key.diffie_hellman(&remote_key);
let mut temp_key = Hmac::new(&self.chaining_key).update(&shared_secret).finalize();
let chaining_key = Hmac::new(&temp_key).update([0x01]).finalize();
let aead_key = Hmac::new(&temp_key).update(&chaining_key).update([0x02]).finalize();
let state =
Sha256::new().update(&self.inbound_state).update(remote_key.to_vec()).finalize();
temp_key.zeroize();
shared_secret.zeroize();
ShortInboundSession::new(chaining_key, aead_key, state)
}
pub fn create_long_inbound_session(
&self,
remote_key: EphemeralPublicKey,
) -> LongInboundSession {
let mut shared_secret = self.local_key.diffie_hellman(&remote_key);
let mut temp_key = Hmac::new(&self.chaining_key).update(&shared_secret).finalize();
let chaining_key = Hmac::new(&temp_key).update([0x01]).finalize();
let aead_key = Hmac::new(&temp_key).update(&chaining_key).update([0x02]).finalize();
let state =
Sha256::new().update(&self.inbound_state).update(remote_key.to_vec()).finalize();
temp_key.zeroize();
shared_secret.zeroize();
LongInboundSession::new(chaining_key, aead_key, state)
}
pub fn derive_inbound_garlic_key(
&self,
ephemeral_key: EphemeralPublicKey,
) -> (Vec<u8>, Vec<u8>) {
let state = Sha256::new()
.update(&self.inbound_state)
.update::<&[u8]>(ephemeral_key.as_ref())
.finalize();
let mut shared_secret = self.local_key.diffie_hellman(&ephemeral_key);
let mut temp_key = Hmac::new(&self.chaining_key).update(&shared_secret).finalize();
let mut chaining_key = Hmac::new(&temp_key).update([0x01]).finalize();
let aead_key = Hmac::new(&temp_key).update(&chaining_key).update([0x02]).finalize();
temp_key.zeroize();
shared_secret.zeroize();
chaining_key.zeroize();
(aead_key, state)
}
pub fn derive_outbound_garlic_key(
&self,
remote_public: StaticPublicKey,
ephemeral_secret: EphemeralPrivateKey,
) -> (Vec<u8>, Vec<u8>) {
let ephemeral_public = ephemeral_secret.public();
let state = Sha256::new()
.update(
Sha256::new()
.update(&self.outbound_state)
.update::<&[u8]>(remote_public.as_ref())
.finalize(),
)
.update::<&[u8]>(ephemeral_public.as_ref())
.finalize();
let mut shared_secret = ephemeral_secret.diffie_hellman(&remote_public);
let mut temp_key = Hmac::new(&self.chaining_key).update(&shared_secret).finalize();
let mut chaining_key = Hmac::new(&temp_key).update([0x01]).finalize();
let aead_key = Hmac::new(&temp_key).update(&chaining_key).update([0x02]).finalize();
temp_key.zeroize();
shared_secret.zeroize();
chaining_key.zeroize();
(aead_key, state)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::RouterId;
#[test]
fn derive_garlic_keys() {
let remote_key = StaticPrivateKey::random(rand::rng());
let remote_router_id = Bytes::from(RouterId::random().to_vec());
let local_key = StaticPrivateKey::random(rand::rng());
let local_router_id = Bytes::from(RouterId::random().to_vec());
let remote_noise = NoiseContext::new(remote_key.clone(), remote_router_id);
let local_noise = NoiseContext::new(local_key, local_router_id);
let ephemeral_secret = EphemeralPrivateKey::random(rand::rng());
let ephemeral_public = ephemeral_secret.public();
let (local_key, local_state) =
local_noise.derive_outbound_garlic_key(remote_key.public(), ephemeral_secret);
let (remote_key, remote_state) = remote_noise.derive_inbound_garlic_key(ephemeral_public);
assert_eq!(local_key, remote_key);
assert_eq!(local_state, remote_state);
}
}