use super::{build::SurbPayloadEncryptionKeys, crypto::*, delay::Delay, packet::*, target::Target};
use arrayref::{array_mut_ref, array_ref, array_refs};
use subtle::ConstantTimeEq;
pub fn kx_public(packet: &Packet) -> &KxPublic {
array_ref![packet, 0, KX_PUBLIC_SIZE]
}
#[derive(Debug, PartialEq, Eq)]
pub enum Action {
ForwardTo { target: Target, delay: Delay },
DeliverRequest,
DeliverReply { surb_id: SurbId },
DeliverCover { cover_id: Option<CoverId> },
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum PeelErr {
#[error("Bad MAC in header")]
Mac,
#[error("Bad action in header")]
Action,
#[error("Bad payload tag")]
PayloadTag,
}
fn check_payload_tag(tag: &PayloadTag) -> Result<(), PeelErr> {
let tag_ok: bool = tag.ct_eq(&PAYLOAD_TAG).into();
if tag_ok {
Ok(())
} else {
Err(PeelErr::PayloadTag)
}
}
pub fn peel(
out: &mut Packet,
packet: &Packet,
kx_shared_secret: &SharedSecret,
) -> Result<Action, PeelErr> {
let (kx_public, mac, actions, payload) =
array_refs![packet, KX_PUBLIC_SIZE, MAC_SIZE, ACTIONS_SIZE, PAYLOAD_SIZE];
let sds = SmallDerivedSecrets::new(kx_shared_secret);
if !mac_ok(mac, actions, sds.mac_key()) {
return Err(PeelErr::Mac)
}
let decrypted_actions =
array_mut_ref![out, KX_PUBLIC_SIZE - RAW_ACTION_SIZE, ACTIONS_SIZE + MAX_ACTIONS_PAD_SIZE];
*array_mut_ref![decrypted_actions, 0, ACTIONS_SIZE] = *actions;
*array_mut_ref![decrypted_actions, ACTIONS_SIZE, MAX_ACTIONS_PAD_SIZE] =
[0; MAX_ACTIONS_PAD_SIZE]; apply_actions_encryption_keystream(decrypted_actions, sds.actions_encryption_key());
let raw_action = RawAction::from_le_bytes(*array_ref![decrypted_actions, 0, RAW_ACTION_SIZE]);
Ok(match raw_action {
RAW_ACTION_DELIVER_REQUEST => {
let out = array_mut_ref![out, 0, PAYLOAD_SIZE];
*out = *payload;
decrypt_payload(out, &derive_payload_encryption_key(kx_shared_secret));
check_payload_tag(array_ref![out, PAYLOAD_DATA_SIZE, PAYLOAD_TAG_SIZE])?;
Action::DeliverRequest
},
RAW_ACTION_DELIVER_REPLY => {
let surb_id = *array_ref![decrypted_actions, RAW_ACTION_SIZE, SURB_ID_SIZE];
*array_mut_ref![out, 0, PAYLOAD_SIZE] = *payload;
Action::DeliverReply { surb_id }
},
RAW_ACTION_DELIVER_COVER => Action::DeliverCover { cover_id: None },
RAW_ACTION_DELIVER_COVER_WITH_ID => {
let cover_id = *array_ref![decrypted_actions, RAW_ACTION_SIZE, COVER_ID_SIZE];
Action::DeliverCover { cover_id: Some(cover_id) }
},
_ => {
let target = if raw_action == RAW_ACTION_FORWARD_TO_PEER_ID {
let peer_id = *array_ref![decrypted_actions, RAW_ACTION_SIZE, PEER_ID_SIZE];
decrypted_actions.copy_within(
RAW_ACTION_SIZE + PEER_ID_SIZE..
RAW_ACTION_SIZE + PEER_ID_SIZE + MAC_SIZE + ACTIONS_SIZE,
RAW_ACTION_SIZE,
);
Target::PeerId(peer_id)
} else {
Target::MixnodeIndex(raw_action.try_into().map_err(|_| PeelErr::Action)?)
};
let delay = Delay::exp(sds.delay_seed());
*array_mut_ref![out, 0, KX_PUBLIC_SIZE] = blind_kx_public(kx_public, kx_shared_secret);
let out_payload = array_mut_ref![out, HEADER_SIZE, PAYLOAD_SIZE];
*out_payload = *payload;
decrypt_payload(out_payload, &derive_payload_encryption_key(kx_shared_secret));
Action::ForwardTo { target, delay }
},
})
}
pub fn decrypt_reply_payload(
payload: &mut Payload,
keys: &SurbPayloadEncryptionKeys,
) -> Result<(), PeelErr> {
for key in keys.iter().rev() {
encrypt_payload(payload, key);
}
check_payload_tag(array_ref![payload, PAYLOAD_DATA_SIZE, PAYLOAD_TAG_SIZE])
}