Merge branch 'hs-cells-1' into 'main'
Start refactoring hs cell implementations See merge request tpo/core/arti!1020
This commit is contained in:
commit
878531af90
|
@ -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;
|
||||
|
|
|
@ -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 {}
|
||||
}
|
|
@ -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?)
|
||||
}
|
|
@ -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;
|
|
@ -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")]
|
||||
|
|
|
@ -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 {}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue