Merge branch 'hs-cells-1' into 'main'

Start refactoring hs cell implementations

See merge request tpo/core/arti!1020
This commit is contained in:
Nick Mathewson 2023-02-16 15:36:31 +00:00
commit 878531af90
7 changed files with 690 additions and 491 deletions

View File

@ -10,9 +10,9 @@ use caret::caret_int;
use rand::{CryptoRng, Rng};
pub mod extend;
pub mod msg;
#[cfg(feature = "onion-service")]
pub mod onion_service;
pub mod hs;
pub mod msg;
pub mod restrict;
#[cfg(feature = "experimental-udp")]
pub mod udp;

View File

@ -0,0 +1,244 @@
//! Encoding and decoding for relay messages related to onion services.
#![allow(dead_code, unused_variables)] // TODO hs: remove.
// TODO hs: we'll need accessors for the useful fields in all these types.
use super::msg::{self, Body};
use caret::caret_int;
use tor_bytes::{EncodeError, EncodeResult, Error as BytesError, Result};
use tor_bytes::{Reader, Writer};
use tor_hscrypto::RendCookie;
use tor_llcrypto::pk::rsa::RsaIdentity;
pub mod est_intro;
mod ext;
pub use ext::UnrecognizedExt;
caret_int! {
/// The type of the introduction point auth key
pub struct AuthKeyType(u8) {
/// Ed25519; SHA3-256
ED25519_SHA3_256 = 2,
}
}
/// A message sent from client to rendezvous point.
#[derive(Debug, Clone)]
pub struct EstablishRendezvous {
/// A rendezvous cookie is an arbitrary 20-byte value,
/// chosen randomly by the client.
cookie: [u8; EstablishRendezvous::COOKIE_LEN], // TODO hs: Make this a RendCookie.
}
impl EstablishRendezvous {
/// The only acceptable length of a rendezvous cookie.
pub const COOKIE_LEN: usize = 20;
/// Construct a new establish rendezvous cell.
pub fn new(cookie: [u8; Self::COOKIE_LEN]) -> Self {
Self { cookie }
}
}
impl msg::Body for EstablishRendezvous {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
let cookie = r.extract()?;
r.take_rest();
Ok(Self { cookie })
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
w.write(&self.cookie)
}
}
#[derive(Debug, Clone)]
/// A message sent from client to introduction point.
pub struct Introduce1(Introduce);
impl msg::Body for Introduce1 {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
Ok(Self(Introduce::decode_from_reader(r)?))
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
self.0.encode_onto(w)
}
}
impl Introduce1 {
/// All arguments constructor
pub fn new(auth_key_type: AuthKeyType, auth_key: Vec<u8>, encrypted: Vec<u8>) -> Self {
Self(Introduce::new(auth_key_type, auth_key, encrypted))
}
}
#[derive(Debug, Clone)]
/// A message sent from introduction point to hidden service host.
pub struct Introduce2(Introduce);
impl msg::Body for Introduce2 {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
Ok(Self(Introduce::decode_from_reader(r)?))
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
self.0.encode_onto(w)
}
}
impl Introduce2 {
/// All arguments constructor
pub fn new(auth_key_type: AuthKeyType, auth_key: Vec<u8>, encrypted: Vec<u8>) -> Self {
Self(Introduce::new(auth_key_type, auth_key, encrypted))
}
}
#[derive(Debug, Clone)]
/// A message body shared by Introduce1 and Introduce2
struct Introduce {
/// Introduction point auth key type and the type of
/// the MAC used in `handshake_auth`.
auth_key_type: AuthKeyType,
/// The public introduction point auth key.
auth_key: Vec<u8>,
/// Up to end of relay payload.
encrypted: Vec<u8>,
}
impl Introduce {
/// All arguments constructor
fn new(auth_key_type: AuthKeyType, auth_key: Vec<u8>, encrypted: Vec<u8>) -> Self {
Self {
auth_key_type,
auth_key,
encrypted,
}
}
/// Decode an Introduce message body from the given reader
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
let legacy_key_id: RsaIdentity = r.extract()?;
if !legacy_key_id.is_zero() {
return Err(BytesError::InvalidMessage(
"legacy key id in Introduce1.".into(),
));
}
let auth_key_type = r.take_u8()?.into();
let auth_key_len = r.take_u16()?;
let auth_key = r.take(auth_key_len as usize)?.into();
let n_ext = r.take_u8()?;
for _ in 0..n_ext {
let _ext_type = r.take_u8()?;
r.read_nested_u8len(|r| {
r.take_rest();
Ok(())
})?;
}
let encrypted = r.take_rest().into();
Ok(Self {
auth_key_type,
auth_key,
encrypted,
})
}
/// Encode an Introduce message body onto the given writer
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
w.write_all(&[0_u8; 20]);
w.write_u8(self.auth_key_type.get());
w.write_u16(u16::try_from(self.auth_key.len()).map_err(|_| EncodeError::BadLengthValue)?);
w.write_all(&self.auth_key[..]);
// No Introduce1 extension for now.
w.write_u8(0_u8);
w.write_all(&self.encrypted[..]);
Ok(())
}
}
/// A message sent from an onion service to a rendezvous point, telling it to
/// make a connection to the client.
#[derive(Debug, Clone)]
pub struct Rendezvous1 {
/// The cookie originally sent by the client in its ESTABLISH_REND message.
cookie: RendCookie,
/// The message to send the client.
message: Vec<u8>,
}
impl Body for Rendezvous1 {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
todo!()
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
todo!()
}
}
/// A message sent from the rendezvous point to the client, telling it about the
/// onion service's message.
#[derive(Debug, Clone)]
pub struct Rendezvous2 {
/// The message from the onion service.
message: Vec<u8>,
}
impl Body for Rendezvous2 {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
todo!()
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
todo!()
}
}
/// Reply sent from the introduction point to the onion service, telling it that
/// an introduction point is now established.
#[derive(Debug, Clone)]
pub struct IntroEstablished {
/// The extensions included in this cell.
//
// TODO hs: we should extract this with any DOS related extensions, depending on what we
// decide to do with extension in general.
extensions: Vec<IntroEstExtension>,
}
impl Body for IntroEstablished {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
todo!()
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
todo!()
}
}
/// An extension included in an [`IntroEstablished`] message.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum IntroEstExtension {
/// An unrecognized extension.
Unrecognized(Vec<u8>),
}
/// A reply from the introduction point to the client, telling it that its
/// introduce1 was received.
#[derive(Clone, Debug)]
pub struct IntroduceAck {
// TODO hs: use a caret enum for this.
/// The status reported for the Introduce1 message.
status_code: u16,
// TODO hs: add extensions.
}
impl Body for IntroduceAck {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
todo!()
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
todo!()
}
}
super::msg::empty_body! {
/// Acknowledges an EstablishRendezvous message.
pub struct RendezvousEstablished {}
}

View File

@ -0,0 +1,203 @@
//! Define the ESTABLISH_INTRO message and related types.
use caret::caret_int;
use tor_bytes::{EncodeError, EncodeResult, Readable, Reader, Result, Writeable, Writer};
use tor_units::BoundedInt32;
use crate::relaycell::{hs::ext::*, hs::AuthKeyType, msg};
caret_int! {
/// The introduction protocol extension type
#[derive(Ord, PartialOrd)]
pub struct EstIntroExtType(u8) {
/// The extension used to send DoS parameters
DOS_PARAMS = 1,
}
}
caret_int! {
/// The recognized parameter types in an establish intro
/// DoS extension.
pub struct EstIntroExtDosParamType(u8) {
/// The rate per second of INTRODUCE2 cell relayed
/// to the service.
DOS_INTRODUCE2_RATE_PER_SEC = 1,
/// The burst per second of INTRODUCE2 cell relayed
/// to the service
DOS_INTRODUCE2_BURST_PER_SEC = 2,
}
}
/// An establish Introduction protocol extension
#[derive(Debug, Clone)]
pub struct DosParams {
/// An optional parameter indicates the rate per second of
/// INTRODUCE2 cell relayed to the service.
///
/// Min: 0, Max: 2147483647
rate_per_sec: Option<BoundedInt32<0, { i32::MAX }>>,
/// An optional parameter indicates the burst per second of
/// INTRODUCE2 cell relayed to the service
///
/// Min: 0, Max: 2147483647
burst_per_sec: Option<BoundedInt32<0, { i32::MAX }>>,
}
impl DosParams {
/// Create a new establish intro DoS extension.
pub fn new(rate_per_sec: Option<i32>, burst_per_sec: Option<i32>) -> crate::Result<Self> {
let normalize = |supplied: Option<i32>| -> crate::Result<_> {
supplied
.map(|val| {
BoundedInt32::checked_new(val).map_err(|_| {
crate::err::Error::CantEncode(
"EST_INTRO_DOS_EXT parameter value out of bound.",
)
})
})
.transpose()
};
Ok(Self {
rate_per_sec: normalize(rate_per_sec)?,
burst_per_sec: normalize(burst_per_sec)?,
})
}
}
impl Ext for DosParams {
type Id = EstIntroExtType;
fn type_id(&self) -> EstIntroExtType {
EstIntroExtType::DOS_PARAMS
}
fn take_body_from(b: &mut Reader<'_>) -> Result<Self> {
let n_prams = b.take_u8()?;
let mut rate_per_sec = None;
let mut burst_per_sec = None;
for _i in 0..n_prams {
let param_to_store = match b.take_u8()?.into() {
EstIntroExtDosParamType::DOS_INTRODUCE2_RATE_PER_SEC => Some(&mut rate_per_sec),
EstIntroExtDosParamType::DOS_INTRODUCE2_BURST_PER_SEC => Some(&mut burst_per_sec),
_ => None,
};
if let Some(param) = param_to_store {
if let Ok(rate) = i32::try_from(b.take_u64()?) {
*param = BoundedInt32::checked_new(rate).ok();
}
}
}
Ok(Self {
rate_per_sec,
burst_per_sec,
})
}
fn write_body_onto<B: Writer + ?Sized>(&self, b: &mut B) -> EncodeResult<()> {
let mut params = vec![];
let mut push_params = |ty, value| {
if let Some(value) = value {
params.push((ty, value));
}
};
push_params(
EstIntroExtDosParamType::DOS_INTRODUCE2_RATE_PER_SEC,
self.rate_per_sec,
);
push_params(
EstIntroExtDosParamType::DOS_INTRODUCE2_BURST_PER_SEC,
self.burst_per_sec,
);
b.write_u8(u8::try_from(params.len()).map_err(|_| EncodeError::BadLengthValue)?);
for (t, v) in params {
b.write_u8(t.get());
b.write_u64(v.get() as u64);
}
Ok(())
}
}
decl_extension_group! {
/// An extension to an EstablishIntro cell.
#[derive(Debug,Clone)]
enum EstablishIntroExt [ EstIntroExtType ] {
DosParams,
}
}
/// A hidden services establishes a new introduction point,
/// by sending an EstablishIntro message.
#[derive(Debug, Clone)]
pub struct EstablishIntro {
/// Introduction point auth key type and the type of
/// the MAC used in `handshake_auth`.
auth_key_type: AuthKeyType,
/// The public introduction point auth key.
auth_key: Vec<u8>,
/// A list of extensions on this cell.
extensions: ExtList<EstablishIntroExt>,
/// the MAC of all earlier fields in the cell.
handshake_auth: [u8; 32],
/// A signature using `auth_key` of all contents
/// of the cell.
sig: Vec<u8>,
}
impl msg::Body for EstablishIntro {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
let auth_key_type = r.take_u8()?.into();
let auth_key_len = r.take_u16()?;
let auth_key = r.take(auth_key_len as usize)?.into();
let extensions = r.extract()?;
let handshake_auth = r.extract()?;
let sig_len = r.take_u16()?;
let sig = r.take(sig_len as usize)?.into();
Ok(EstablishIntro {
auth_key_type,
auth_key,
extensions,
handshake_auth,
sig,
})
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
w.write_u8(self.auth_key_type.get());
w.write_u16(u16::try_from(self.auth_key.len()).map_err(|_| EncodeError::BadLengthValue)?);
w.write_all(&self.auth_key[..]);
w.write(&self.extensions)?;
w.write_all(&self.handshake_auth[..]);
w.write_u16(u16::try_from(self.sig.len()).map_err(|_| EncodeError::BadLengthValue)?);
w.write_all(&self.sig[..]);
Ok(())
}
}
impl EstablishIntro {
/// All arguments constructor
pub fn new(
auth_key_type: AuthKeyType,
auth_key: Vec<u8>,
handshake_auth: [u8; 32],
sig: Vec<u8>,
) -> Self {
Self {
auth_key_type,
auth_key,
handshake_auth,
sig,
extensions: Default::default(),
}
}
/// Set EST_INTRO_DOS_EXT with given `extension_dos`.
pub fn set_extension_dos(&mut self, extension_dos: DosParams) {
self.extensions.replace_by_type(extension_dos.into());
}
/// Add an extension of some other type.
pub fn set_extension_other(&mut self, other: UnrecognizedExt<EstIntroExtType>) {
self.extensions.replace_by_type(other.into());
}
// TODO hs: we'll need accessors.
//
// TODO hs: we will need some way to ensure that the mac is valid and well-signed. Possibly
// we should look into using a SignatureGated (if we can reasonably do so?)
}

View File

@ -0,0 +1,219 @@
//! Helpers to manage lists of HS cell extensions.
//
// TODO: We might generalize this even more in the future to handle other
// similar lists in our cell protocol.
use tor_bytes::{EncodeError, EncodeResult, Readable, Reader, Result, Writeable, Writer};
/// A list of extensions, represented in a common format used by many HS-related
/// message.
///
/// The common format is:
/// ```text
/// N_EXTENSIONS [1 byte]
/// N_EXTENSIONS times:
/// EXT_FIELD_TYPE [1 byte]
/// EXT_FIELD_LEN [1 byte]
/// EXT_FIELD [EXT_FIELD_LEN bytes]
/// ```
///
/// It is subject to the additional restraints:
///
/// * Each extension type SHOULD be sent only once in a message.
/// * Parties MUST ignore any occurrences all occurrences of an extension
/// with a given type after the first such occurrence.
/// * Extensions SHOULD be sent in numerically ascending order by type.
#[derive(Clone, Debug, derive_more::Deref, derive_more::DerefMut)]
pub(super) struct ExtList<T> {
/// The extensions themselves.
extensions: Vec<T>,
}
impl<T> Default for ExtList<T> {
fn default() -> Self {
Self {
extensions: Vec::new(),
}
}
}
/// An kind of extension that can be used with some kind of HS-related message.
///
/// Each extendible message will likely define its own enum,
/// implementing this trait,
/// representing the possible extensions.
pub(super) trait ExtGroup: Readable + Writeable {
/// An identifier kind used with this sort of extension
type Id: From<u8> + Into<u8> + Eq + PartialEq + Ord + Copy;
/// The field-type id for this particular extension.
fn type_id(&self) -> Self::Id;
}
/// A single typed extension that can be used with some kind of HS-related message.
pub(super) trait Ext: Sized {
/// An identifier kind used with this sort of extension.
///
/// Typically defined with caret_int.
type Id: From<u8> + Into<u8>;
/// The field-type id for this particular extension.
fn type_id(&self) -> Self::Id;
/// Extract the body (not the type or the length) from a single
/// extension.
fn take_body_from(b: &mut Reader<'_>) -> Result<Self>;
/// Write the body (not the type or the length) for a single extension.
fn write_body_onto<B: Writer + ?Sized>(&self, b: &mut B) -> EncodeResult<()>;
}
impl<T: ExtGroup> Readable for ExtList<T> {
fn take_from(b: &mut Reader<'_>) -> Result<Self> {
let n_extensions = b.take_u8()?;
let extensions: Result<Vec<T>> = (0..n_extensions).map(|_| b.extract::<T>()).collect();
Ok(Self {
extensions: extensions?,
})
}
}
impl<T: ExtGroup> Writeable for ExtList<T> {
fn write_onto<B: Writer + ?Sized>(&self, b: &mut B) -> EncodeResult<()> {
let n_extensions = self
.extensions
.len()
.try_into()
.map_err(|_| EncodeError::BadLengthValue)?;
b.write_u8(n_extensions);
let mut exts_sorted: Vec<&T> = self.extensions.iter().collect();
exts_sorted.sort_by_key(|ext| ext.type_id());
exts_sorted.iter().try_for_each(|ext| ext.write_onto(b))?;
Ok(())
}
}
impl<T: ExtGroup> ExtList<T> {
/// Insert `ext` into this list of extensions, replacing any previous
/// extension with the same field type ID.
pub(super) fn replace_by_type(&mut self, ext: T) {
self.retain(|e| e.type_id() != ext.type_id());
self.push(ext);
}
/// Find the first entry (if any) in this list of extensions with a given
/// type ID.
///
/// (We do not enforce uniqueness here, since the spec explicitly says that
/// we should ignore all but the first extension of a given type.)
pub(super) fn by_type(&self, id: T::Id) -> Option<&T> {
self.iter().find(|e| e.type_id() == id)
}
}
/// An unrecognized extension for some HS-related message.
#[derive(Clone, Debug)]
pub struct UnrecognizedExt<ID> {
/// The field type ID for this extension.
pub(super) type_id: ID,
/// The body of this extension.
pub(super) body: Vec<u8>,
}
impl<ID> UnrecognizedExt<ID> {
/// Return a new unrecognized extension with a given ID and body.
///
/// NOTE: nothing actually enforces that this type ID is not
/// recognized.
///
/// NOTE: This function accepts bodies longer than 255 bytes, but
/// it is not possible to encode them.
pub fn new(type_id: ID, body: impl Into<Vec<u8>>) -> Self {
Self {
type_id,
body: body.into(),
}
}
}
/// Declare an Extension group that takes a given identifier.
//
// TODO: This is rather similar to restrict_msg(), isn't it? Also, We use this
// pattern of (number, (cmd, length, body)*) a few of times in Tor outside the
// hs module. Perhaps we can extend and unify our code here...
macro_rules! decl_extension_group {
{
$( #[$meta:meta] )*
$v:vis enum $id:ident [ $type_id:ty ] {
$(
$(#[$cmeta:meta])*
$case:ident),*
$(,)?
}
} => {paste::paste!{
$( #[$meta] )*
$v enum $id {
$( $(#[$cmeta])*
$case($case),
)*
/// An extension of a type we do not recognize.
Unrecognized(UnrecognizedExt<$type_id>)
}
impl Readable for $id {
fn take_from(b: &mut Reader<'_>) -> Result<Self> {
let type_id = b.take_u8()?.into();
Ok(match type_id {
$(
$type_id::[< $case:snake:upper >] => {
Self::$case( b.read_nested_u8len(|r| $case::take_body_from(r))? )
}
)*
other => {
Self::Unrecognized(UnrecognizedExt {
type_id,
body: b.read_nested_u8len(|r| Ok(r.take_rest().into()))?,
})
}
})
}
}
impl Writeable for $id {
fn write_onto<B: Writer + ?Sized>(&self, b: &mut B) -> EncodeResult<
()> {
#[allow(unused)]
use std::ops::DerefMut;
match self {
$(
Self::$case(val) => {
b.write_u8(val.type_id().into());
let mut nested = b.write_nested_u8len();
val.write_body_onto(nested.deref_mut())?;
nested.finish()?;
}
)*
Self::Unrecognized(unrecognized) => {
b.write_u8(unrecognized.type_id.into());
let mut nested = b.write_nested_u8len();
nested.write_all(&unrecognized.body[..]);
nested.finish()?;
}
}
Ok(())
}
}
impl ExtGroup for $id {
type Id = $type_id;
fn type_id(&self) -> Self::Id {
match self {
$(
Self::$case(val) => val.type_id(),
)*
Self::Unrecognized(unrecognized) => unrecognized.type_id,
}
}
}
$(
impl From<$case> for $id {
fn from(val: $case) -> $id {
$id :: $case ( val )
}
}
)*
impl From<UnrecognizedExt<$type_id>> for $id {
fn from(val: UnrecognizedExt<$type_id>) -> $id {
$id :: Unrecognized(val)
}
}
}}
}
pub(super) use decl_extension_group;

View File

@ -19,9 +19,9 @@ use bitflags::bitflags;
#[cfg_attr(docsrs, doc(cfg(feature = "onion-service")))]
#[cfg(feature = "onion-service")]
pub use super::onion_service::{
EstablishIntro, EstablishRendezvous, IntroEstablished, Introduce1, Introduce2, IntroduceAck,
Rendezvous1, Rendezvous2, RendezvousEstablished,
pub use super::hs::{
est_intro::EstablishIntro, EstablishRendezvous, IntroEstablished, Introduce1, Introduce2,
IntroduceAck, Rendezvous1, Rendezvous2, RendezvousEstablished,
};
#[cfg_attr(docsrs, doc(cfg(feature = "experimental-udp")))]
#[cfg(feature = "experimental-udp")]

View File

@ -1,459 +0,0 @@
//! Encoding and decoding for relay messages related to onion services.
#![allow(dead_code, unused_variables)] // TODO hs: remove.
// TODO hs design: We need to discuss how we want to handle extensions in these
// cells: extensions are used all over the protocol, and it would be nice to be
// more clever about them.
//
// It would be neat if recognized extension types were decoded into recognized
// structures. On the other hand, it would be good to retain unrecognized
// extension types, so that other code can use them in the future without having to
// add support here.
// TODO hs: we'll need accessors for the useful fields in all these types.
use super::msg::{self, Body};
use caret::caret_int;
use tor_bytes::{EncodeError, EncodeResult, Error as BytesError, Readable, Result, Writeable};
use tor_bytes::{Reader, Writer};
use tor_hscrypto::RendCookie;
use tor_llcrypto::pk::rsa::RsaIdentity;
use tor_units::BoundedInt32;
caret_int! {
/// The type of the introduction point auth key
pub struct AuthKeyType(u8) {
/// Ed25519; SHA3-256
ED25519_SHA3_256 = 2,
}
}
caret_int! {
/// The introduction protocol extension type
pub struct EstIntroExtType(u8) {
/// The extension used to send DoS parameters
EST_INTRO_DOS_EXT = 1,
}
}
caret_int! {
/// The recognized parameter types in an establish intro
/// DoS extension.
pub struct EstIntroExtDosParamType(u8) {
/// The rate per second of INTRODUCE2 cell relayed
/// to the service.
DOS_INTRODUCE2_RATE_PER_SEC = 1,
/// The burst per second of INTRODUCE2 cell relayed
/// to the service
DOS_INTRODUCE2_BURST_PER_SEC = 2,
}
}
/// An establish Introduction protocol extension
#[derive(Debug, Clone)]
pub struct EstIntroExtDoS {
/// An optional parameter indicates the rate per second of
/// INTRODUCE2 cell relayed to the service.
///
/// Min: 0, Max: 2147483647
rate_per_sec: Option<BoundedInt32<0, { i32::MAX }>>,
/// An optional parameter indicates the burst per second of
/// INTRODUCE2 cell relayed to the service
///
/// Min: 0, Max: 2147483647
burst_per_sec: Option<BoundedInt32<0, { i32::MAX }>>,
}
impl EstIntroExtDoS {
/// Create a new establish intro DoS extension.
pub fn new(rate_per_sec: Option<i32>, burst_per_sec: Option<i32>) -> crate::Result<Self> {
let normalize = |supplied: Option<i32>| -> crate::Result<_> {
supplied
.map(|val| {
BoundedInt32::checked_new(val).map_err(|_| {
crate::err::Error::CantEncode(
"EST_INTRO_DOS_EXT parameter value out of bound.",
)
})
})
.transpose()
};
Ok(Self {
rate_per_sec: normalize(rate_per_sec)?,
burst_per_sec: normalize(burst_per_sec)?,
})
}
}
impl Readable for EstIntroExtDoS {
fn take_from(b: &mut Reader<'_>) -> Result<Self> {
let n_prams = b.take_u8()?;
let mut rate_per_sec = None;
let mut burst_per_sec = None;
for _i in 0..n_prams {
let param_to_store = match b.take_u8()?.into() {
EstIntroExtDosParamType::DOS_INTRODUCE2_RATE_PER_SEC => Some(&mut rate_per_sec),
EstIntroExtDosParamType::DOS_INTRODUCE2_BURST_PER_SEC => Some(&mut burst_per_sec),
_ => None,
};
if let Some(param) = param_to_store {
if let Ok(rate) = i32::try_from(b.take_u64()?) {
*param = BoundedInt32::checked_new(rate).ok();
}
}
}
Ok(Self {
rate_per_sec,
burst_per_sec,
})
}
}
impl Writeable for EstIntroExtDoS {
fn write_onto<B: Writer + ?Sized>(&self, b: &mut B) -> EncodeResult<()> {
let mut params = vec![];
let mut push_params = |ty, value| {
if let Some(value) = value {
params.push((ty, value));
}
};
push_params(
EstIntroExtDosParamType::DOS_INTRODUCE2_RATE_PER_SEC,
self.rate_per_sec,
);
push_params(
EstIntroExtDosParamType::DOS_INTRODUCE2_BURST_PER_SEC,
self.burst_per_sec,
);
b.write_u8(u8::try_from(params.len()).map_err(|_| EncodeError::BadLengthValue)?);
for (t, v) in params {
b.write_u8(t.get());
b.write_u64(v.get() as u64);
}
Ok(())
}
}
/// A hidden services establishes a new introduction point,
/// by sending an EstablishIntro message.
#[derive(Debug, Clone)]
pub struct EstablishIntro {
/// Introduction point auth key type and the type of
/// the MAC used in `handshake_auth`.
auth_key_type: AuthKeyType,
/// The public introduction point auth key.
auth_key: Vec<u8>,
/// An optional denial-of-service extension.
//
// TODO hs: we may want to consider making this a vector of extensions instead,
// to allow unrecognized extensions?
extension_dos: Option<EstIntroExtDoS>,
/// the MAC of all earlier fields in the cell.
handshake_auth: [u8; 32],
/// A signature using `auth_key` of all contents
/// of the cell.
sig: Vec<u8>,
}
impl msg::Body for EstablishIntro {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
let auth_key_type = r.take_u8()?.into();
let auth_key_len = r.take_u16()?;
let auth_key = r.take(auth_key_len as usize)?.into();
let n_ext = r.take_u8()?;
let mut extension_dos = None;
for _ in 0..n_ext {
let ext_type: EstIntroExtType = r.take_u8()?.into();
r.read_nested_u8len(|r| {
if ext_type == EstIntroExtType::EST_INTRO_DOS_EXT {
extension_dos.get_or_insert(r.extract()?);
} else {
r.take_rest();
}
Ok(())
})?;
}
let handshake_auth = r.extract()?;
let sig_len = r.take_u16()?;
let sig = r.take(sig_len as usize)?.into();
Ok(EstablishIntro {
auth_key_type,
auth_key,
extension_dos,
handshake_auth,
sig,
})
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
w.write_u8(self.auth_key_type.get());
w.write_u16(u16::try_from(self.auth_key.len()).map_err(|_| EncodeError::BadLengthValue)?);
w.write_all(&self.auth_key[..]);
let mut extensions: Vec<(EstIntroExtType, Vec<u8>)> = vec![];
if let Some(extension_dos) = self.extension_dos {
let mut extension = vec![];
extension.write(&extension_dos)?;
extensions.push((EstIntroExtType::EST_INTRO_DOS_EXT, extension));
}
w.write_u8(u8::try_from(extensions.len()).map_err(|_| EncodeError::BadLengthValue)?);
for (t, v) in extensions {
w.write_u8(t.get());
let mut w = w.write_nested_u8len();
w.write(&v)?;
w.finish()?;
}
w.write_all(&self.handshake_auth[..]);
w.write_u16(u16::try_from(self.sig.len()).map_err(|_| EncodeError::BadLengthValue)?);
w.write_all(&self.sig[..]);
Ok(())
}
}
impl EstablishIntro {
/// All arguments constructor
pub fn new(
auth_key_type: AuthKeyType,
auth_key: Vec<u8>,
handshake_auth: [u8; 32],
sig: Vec<u8>,
) -> Self {
Self {
auth_key_type,
auth_key,
handshake_auth,
sig,
extension_dos: None,
}
}
/// Set EST_INTRO_DOS_EXT with given `extension_dos`.
pub fn set_extension_dos(&mut self, extension_dos: EstIntroExtDoS) {
self.extension_dos = Some(extension_dos);
}
// TODO hs: we'll need accessors.
//
// TODO hs: we will need some way to ensure that the mac is valid and well-signed. Possibly
// we should look into using a SignatureGated (if we can reasonably do so?)
}
/// A message sent from client to rendezvous point.
#[derive(Debug, Clone)]
pub struct EstablishRendezvous {
/// A rendezvous cookie is an arbitrary 20-byte value,
/// chosen randomly by the client.
cookie: [u8; EstablishRendezvous::COOKIE_LEN], // TODO hs: Make this a RendCookie.
}
impl EstablishRendezvous {
/// The only acceptable length of a rendezvous cookie.
pub const COOKIE_LEN: usize = 20;
/// Construct a new establish rendezvous cell.
pub fn new(cookie: [u8; Self::COOKIE_LEN]) -> Self {
Self { cookie }
}
}
impl msg::Body for EstablishRendezvous {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
let cookie = r.extract()?;
r.take_rest();
Ok(Self { cookie })
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
w.write(&self.cookie)
}
}
#[derive(Debug, Clone)]
/// A message sent from client to introduction point.
pub struct Introduce1(Introduce);
impl msg::Body for Introduce1 {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
Ok(Self(Introduce::decode_from_reader(r)?))
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
self.0.encode_onto(w)
}
}
impl Introduce1 {
/// All arguments constructor
pub fn new(auth_key_type: AuthKeyType, auth_key: Vec<u8>, encrypted: Vec<u8>) -> Self {
Self(Introduce::new(auth_key_type, auth_key, encrypted))
}
}
#[derive(Debug, Clone)]
/// A message sent from introduction point to hidden service host.
pub struct Introduce2(Introduce);
impl msg::Body for Introduce2 {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
Ok(Self(Introduce::decode_from_reader(r)?))
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
self.0.encode_onto(w)
}
}
impl Introduce2 {
/// All arguments constructor
pub fn new(auth_key_type: AuthKeyType, auth_key: Vec<u8>, encrypted: Vec<u8>) -> Self {
Self(Introduce::new(auth_key_type, auth_key, encrypted))
}
}
#[derive(Debug, Clone)]
/// A message body shared by Introduce1 and Introduce2
struct Introduce {
/// Introduction point auth key type and the type of
/// the MAC used in `handshake_auth`.
auth_key_type: AuthKeyType,
/// The public introduction point auth key.
auth_key: Vec<u8>,
/// Up to end of relay payload.
encrypted: Vec<u8>,
}
impl Introduce {
/// All arguments constructor
fn new(auth_key_type: AuthKeyType, auth_key: Vec<u8>, encrypted: Vec<u8>) -> Self {
Self {
auth_key_type,
auth_key,
encrypted,
}
}
/// Decode an Introduce message body from the given reader
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
let legacy_key_id: RsaIdentity = r.extract()?;
if !legacy_key_id.is_zero() {
return Err(BytesError::InvalidMessage(
"legacy key id in Introduce1.".into(),
));
}
let auth_key_type = r.take_u8()?.into();
let auth_key_len = r.take_u16()?;
let auth_key = r.take(auth_key_len as usize)?.into();
let n_ext = r.take_u8()?;
for _ in 0..n_ext {
let _ext_type = r.take_u8()?;
r.read_nested_u8len(|r| {
r.take_rest();
Ok(())
})?;
}
let encrypted = r.take_rest().into();
Ok(Self {
auth_key_type,
auth_key,
encrypted,
})
}
/// Encode an Introduce message body onto the given writer
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
w.write_all(&[0_u8; 20]);
w.write_u8(self.auth_key_type.get());
w.write_u16(u16::try_from(self.auth_key.len()).map_err(|_| EncodeError::BadLengthValue)?);
w.write_all(&self.auth_key[..]);
// No Introduce1 extension for now.
w.write_u8(0_u8);
w.write_all(&self.encrypted[..]);
Ok(())
}
}
/// A message sent from an onion service to a rendezvous point, telling it to
/// make a connection to the client.
#[derive(Debug, Clone)]
pub struct Rendezvous1 {
/// The cookie originally sent by the client in its ESTABLISH_REND message.
cookie: RendCookie,
/// The message to send the client.
message: Vec<u8>,
}
impl Body for Rendezvous1 {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
todo!()
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
todo!()
}
}
/// A message sent from the rendezvous point to the client, telling it about the
/// onion service's message.
#[derive(Debug, Clone)]
pub struct Rendezvous2 {
/// The message from the onion service.
message: Vec<u8>,
}
impl Body for Rendezvous2 {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
todo!()
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
todo!()
}
}
/// Reply sent from the introduction point to the onion service, telling it that
/// an introduction point is now established.
#[derive(Debug, Clone)]
pub struct IntroEstablished {
/// The extensions included in this cell.
//
// TODO hs: we should extract this with any DOS related extensions, depending on what we
// decide to do with extension in general.
extensions: Vec<IntroEstExtension>,
}
impl Body for IntroEstablished {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
todo!()
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
todo!()
}
}
/// An extension included in an [`IntroEstablished`] message.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum IntroEstExtension {
/// An unrecognized extension.
Unrecognized(Vec<u8>),
}
/// A reply from the introduction point to the client, telling it that its
/// introduce1 was received.
#[derive(Clone, Debug)]
pub struct IntroduceAck {
// TODO hs: use a caret enum for this.
/// The status reported for the Introduce1 message.
status_code: u16,
// TODO hs: add extensions.
}
impl Body for IntroduceAck {
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
todo!()
}
fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
todo!()
}
}
super::msg::empty_body! {
/// Acknowledges an EstablishRendezvous message.
pub struct RendezvousEstablished {}
}

View File

@ -15,7 +15,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use hex_literal::hex;
#[cfg(feature = "onion-service")]
use tor_cell::relaycell::onion_service;
use tor_cell::relaycell::hs;
#[cfg(feature = "experimental-udp")]
use tor_cell::relaycell::udp;
@ -631,7 +631,7 @@ fn test_establish_rendezvous() {
cmd,
// 20 ones
"0101010101010101010101010101010101010101",
&onion_service::EstablishRendezvous::new(cookie).into(),
&hs::EstablishRendezvous::new(cookie).into(),
);
// Extra bytes are ignored
@ -658,16 +658,13 @@ fn test_establish_rendezvous() {
#[cfg(feature = "onion-service")]
#[test]
fn test_establish_intro() {
use tor_cell::relaycell::{
msg::AnyRelayMsg,
onion_service::{AuthKeyType, EstIntroExtDoS, EstablishIntro},
};
use tor_cell::relaycell::hs::{est_intro::*, AuthKeyType, UnrecognizedExt};
let cmd = RelayCmd::ESTABLISH_INTRO;
let auth_key_type = AuthKeyType::ED25519_SHA3_256;
let auth_key = vec![0, 1, 2, 3];
let extension_dos = EstIntroExtDoS::new(Some(1_i32), Some(2_i32))
.expect("invalid EST_INTRO_DOS_EXT parameter(s)");
let extension_dos =
DosParams::new(Some(1_i32), Some(2_i32)).expect("invalid EST_INTRO_DOS_EXT parameter(s)");
let handshake_auth = [1; 32];
let sig = vec![0, 1, 2, 3];
assert_eq!(Into::<u8>::into(cmd), 32);
@ -700,34 +697,29 @@ fn test_establish_intro() {
// and one unknown extension
let auth_key = vec![0, 1, 2, 3];
let sig = vec![0, 1, 2, 3];
let extension_dos = EstIntroExtDoS::new(Some(1_i32), Some(2_i32))
.expect("invalid EST_INTRO_DOS_EXT parameter(s)");
let body = "02 0004 00010203
02 01 13 02 01 0000000000000001 02 0000000000000002 02 01 00
0101010101010101010101010101010101010101010101010101010101010101
0004 00010203";
let actual_msg = decode(cmd, &unhex(body)[..]).unwrap();
let mut actual_bytes = vec![];
let mut expect_bytes = vec![];
actual_msg
.encode_onto(&mut actual_bytes)
.expect("Encode msg onto byte vector");
let extension_dos =
DosParams::new(Some(1_i32), Some(2_i32)).expect("invalid EST_INTRO_DOS_EXT parameter(s)");
let extension_unrecognized = UnrecognizedExt::new(2.into(), vec![0]);
let mut es_intro = EstablishIntro::new(auth_key_type, auth_key, handshake_auth, sig);
es_intro.set_extension_dos(extension_dos);
let expected_msg: AnyRelayMsg = es_intro.into();
expected_msg
.encode_onto(&mut expect_bytes)
.expect("Encode msg onto byte vector");
assert_eq!(actual_bytes, expect_bytes);
es_intro.set_extension_other(extension_unrecognized);
msg(
cmd,
"02 0004 00010203
02 01 13 02 01 0000000000000001 02 0000000000000002 02 01 00
0101010101010101010101010101010101010101010101010101010101010101
0004 00010203",
&es_intro.into(),
);
}
#[cfg(feature = "onion-service")]
#[test]
fn test_introduce() {
use tor_cell::relaycell::{
hs::{AuthKeyType, Introduce1},
msg::AnyRelayMsg,
onion_service::{AuthKeyType, Introduce1},
};
// Testing with Introduce1 only should be sufficient as long as