Merge branch 'establish_intro_v2' into 'main'
Implement circuit binding and start on intro-point establisher logic Closes #953 and #993 See merge request tpo/core/arti!1472
This commit is contained in:
commit
118ed81d82
|
@ -4653,6 +4653,7 @@ dependencies = [
|
||||||
"safelog",
|
"safelog",
|
||||||
"serde",
|
"serde",
|
||||||
"signature 1.6.4",
|
"signature 1.6.4",
|
||||||
|
"subtle",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tor-basic-utils",
|
"tor-basic-utils",
|
||||||
"tor-bytes",
|
"tor-bytes",
|
||||||
|
@ -4683,7 +4684,9 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tor-cell",
|
||||||
"tor-circmgr",
|
"tor-circmgr",
|
||||||
|
"tor-error",
|
||||||
"tor-hscrypto",
|
"tor-hscrypto",
|
||||||
"tor-keymgr",
|
"tor-keymgr",
|
||||||
"tor-linkspec",
|
"tor-linkspec",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
ADDED: establish_intro functions now take any impl<Into<HsMacKey>>.
|
|
@ -3,9 +3,10 @@
|
||||||
use caret::caret_int;
|
use caret::caret_int;
|
||||||
use tor_bytes::{EncodeError, EncodeResult, Readable, Reader, Result, Writeable, Writer};
|
use tor_bytes::{EncodeError, EncodeResult, Readable, Reader, Result, Writeable, Writer};
|
||||||
use tor_error::bad_api_usage;
|
use tor_error::bad_api_usage;
|
||||||
use tor_hscrypto::ops::{hs_mac, HS_MAC_LEN};
|
use tor_hscrypto::ops::{HsMacKey, HS_MAC_LEN};
|
||||||
use tor_llcrypto::{
|
use tor_llcrypto::{
|
||||||
pk::ed25519::{self, Ed25519Identity, ED25519_SIGNATURE_LEN},
|
pk::ed25519::{self, Ed25519Identity, ED25519_SIGNATURE_LEN},
|
||||||
|
traits::ShortMac as _,
|
||||||
util::ct::CtByteArray,
|
util::ct::CtByteArray,
|
||||||
};
|
};
|
||||||
use tor_units::BoundedInt32;
|
use tor_units::BoundedInt32;
|
||||||
|
@ -279,10 +280,10 @@ impl EstablishIntroDetails {
|
||||||
/// The MAC key is derived from the circuit handshake between the onion
|
/// The MAC key is derived from the circuit handshake between the onion
|
||||||
/// service and the introduction point. The Ed25519 keypair must match the
|
/// service and the introduction point. The Ed25519 keypair must match the
|
||||||
/// one given as the auth_key for this body.
|
/// one given as the auth_key for this body.
|
||||||
pub fn sign_and_encode(
|
pub fn sign_and_encode<'a>(
|
||||||
self,
|
self,
|
||||||
keypair: &ed25519::Keypair,
|
keypair: &ed25519::Keypair,
|
||||||
mac_key: &[u8],
|
mac_key: impl Into<HsMacKey<'a>>,
|
||||||
) -> crate::Result<Vec<u8>> {
|
) -> crate::Result<Vec<u8>> {
|
||||||
use tor_llcrypto::pk::ed25519::Signer;
|
use tor_llcrypto::pk::ed25519::Signer;
|
||||||
if Ed25519Identity::from(&keypair.public) != self.auth_key {
|
if Ed25519Identity::from(&keypair.public) != self.auth_key {
|
||||||
|
@ -292,7 +293,8 @@ impl EstablishIntroDetails {
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
|
|
||||||
output.write(&self)?;
|
output.write(&self)?;
|
||||||
let mac = hs_mac(mac_key, &output[..]);
|
let mac_key: HsMacKey<'_> = mac_key.into();
|
||||||
|
let mac = mac_key.mac(&output[..]);
|
||||||
output.write(&mac)?;
|
output.write(&mac)?;
|
||||||
let signature = {
|
let signature = {
|
||||||
let mut signed_material = Vec::from(SIG_PREFIX);
|
let mut signed_material = Vec::from(SIG_PREFIX);
|
||||||
|
@ -346,20 +348,17 @@ impl EstablishIntro {
|
||||||
///
|
///
|
||||||
/// On success, return the [`EstablishIntroDetails`] describing how to function
|
/// On success, return the [`EstablishIntroDetails`] describing how to function
|
||||||
/// as an introduction point for this service. On failure, return an error.
|
/// as an introduction point for this service. On failure, return an error.
|
||||||
pub fn check_and_unwrap(
|
pub fn check_and_unwrap<'a>(
|
||||||
self,
|
self,
|
||||||
mac_key: &[u8],
|
mac_key: impl Into<HsMacKey<'a>>,
|
||||||
) -> std::result::Result<EstablishIntroDetails, EstablishIntroSigError> {
|
) -> std::result::Result<EstablishIntroDetails, EstablishIntroSigError> {
|
||||||
use tor_llcrypto::pk::ValidatableSignature;
|
use tor_llcrypto::pk::ValidatableSignature;
|
||||||
// There is a timing side-channel here where, if an attacker wants, they
|
|
||||||
// could tell which of the two fields was incorrect. But that shouldn't
|
let mac_key: HsMacKey<'_> = mac_key.into();
|
||||||
// be exploitable for anything.
|
let mac_okay = mac_key.validate(&self.mac_plaintext, &self.handshake_auth);
|
||||||
//
|
let sig_okay = self.sig.is_valid();
|
||||||
// TODO use subtle here anyway, perhaps?
|
|
||||||
if hs_mac(mac_key, &self.mac_plaintext) != self.handshake_auth {
|
if !(bool::from(mac_okay) & sig_okay) {
|
||||||
return Err(EstablishIntroSigError::Invalid);
|
|
||||||
}
|
|
||||||
if !self.sig.is_valid() {
|
|
||||||
return Err(EstablishIntroSigError::Invalid);
|
return Err(EstablishIntroSigError::Invalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,14 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
full = ["safelog/full", "tor-basic-utils/full", "tor-bytes/full", "tor-llcrypto/full", "tor-units/full", "tor-error/full"]
|
full = [
|
||||||
|
"safelog/full",
|
||||||
|
"tor-basic-utils/full",
|
||||||
|
"tor-bytes/full",
|
||||||
|
"tor-llcrypto/full",
|
||||||
|
"tor-units/full",
|
||||||
|
"tor-error/full",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
data-encoding = "2.3.1" # want MSVC i686 build fix, data-encoding/issues/33
|
data-encoding = "2.3.1" # want MSVC i686 build fix, data-encoding/issues/33
|
||||||
|
@ -26,6 +33,7 @@ rand_core = "0.6.2"
|
||||||
safelog = { path = "../safelog", version = "0.3.2" }
|
safelog = { path = "../safelog", version = "0.3.2" }
|
||||||
serde = { version = "1.0.103", features = ["derive"] }
|
serde = { version = "1.0.103", features = ["derive"] }
|
||||||
signature = "1"
|
signature = "1"
|
||||||
|
subtle = "2"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
tor-basic-utils = { path = "../tor-basic-utils", version = "0.7.3" }
|
tor-basic-utils = { path = "../tor-basic-utils", version = "0.7.3" }
|
||||||
tor-bytes = { version = "0.7.2", path = "../tor-bytes" }
|
tor-bytes = { version = "0.7.2", path = "../tor-bytes" }
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
ADDED: HsMacKey type.
|
|
@ -1,5 +1,4 @@
|
||||||
//! Mid-level cryptographic operations used in the onion service protocol.
|
//! Mid-level cryptographic operations used in the onion service protocol.
|
||||||
|
|
||||||
use tor_llcrypto::d::Sha3_256;
|
use tor_llcrypto::d::Sha3_256;
|
||||||
use tor_llcrypto::util::ct::CtByteArray;
|
use tor_llcrypto::util::ct::CtByteArray;
|
||||||
|
|
||||||
|
@ -27,6 +26,22 @@ pub fn hs_mac(key: &[u8], msg: &[u8]) -> CtByteArray<HS_MAC_LEN> {
|
||||||
a.into()
|
a.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reference to a slice of bytes usable to compute the [`hs_mac`] function.
|
||||||
|
#[derive(Copy, Clone, derive_more::From)]
|
||||||
|
pub struct HsMacKey<'a>(&'a [u8]);
|
||||||
|
|
||||||
|
impl<'a> tor_llcrypto::traits::ShortMac<HS_MAC_LEN> for HsMacKey<'a> {
|
||||||
|
fn mac(&self, input: &[u8]) -> CtByteArray<HS_MAC_LEN> {
|
||||||
|
hs_mac(self.0, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, input: &[u8], mac: &[u8; HS_MAC_LEN]) -> subtle::Choice {
|
||||||
|
use subtle::ConstantTimeEq;
|
||||||
|
let m = hs_mac(self.0, input);
|
||||||
|
m.as_ref().ct_eq(mac)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
// @@ begin test lint list maintained by maint/add_warning @@
|
// @@ begin test lint list maintained by maint/add_warning @@
|
||||||
|
|
|
@ -29,13 +29,15 @@ async-trait = "0.1.54"
|
||||||
futures = "0.3.14"
|
futures = "0.3.14"
|
||||||
rand_core = "0.6.2"
|
rand_core = "0.6.2"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
tor-cell = { version = "0.12.1", path = "../tor-cell", features = ["hs"] }
|
||||||
tor-circmgr = { version = "0.10.0", path = "../tor-circmgr", features = ["hs-service"] }
|
tor-circmgr = { version = "0.10.0", path = "../tor-circmgr", features = ["hs-service"] }
|
||||||
|
tor-error = { version = "0.5.3", path = "../tor-error" }
|
||||||
tor-hscrypto = { version = "0.3.0", path = "../tor-hscrypto" }
|
tor-hscrypto = { version = "0.3.0", path = "../tor-hscrypto" }
|
||||||
tor-keymgr = { version = "0.2.0", path = "../tor-keymgr" }
|
tor-keymgr = { version = "0.2.0", path = "../tor-keymgr" }
|
||||||
tor-linkspec = { version = "0.8.1", path = "../tor-linkspec" }
|
tor-linkspec = { version = "0.8.1", path = "../tor-linkspec" }
|
||||||
tor-llcrypto = { version = "0.5.2", path = "../tor-llcrypto" }
|
tor-llcrypto = { version = "0.5.2", path = "../tor-llcrypto" }
|
||||||
tor-netdir = { version = "0.9.3", path = "../tor-netdir" }
|
tor-netdir = { version = "0.9.3", path = "../tor-netdir" }
|
||||||
tor-proto = { version = "0.12.0", path = "../tor-proto" }
|
tor-proto = { version = "0.12.0", path = "../tor-proto", features = ["hs-service", "send-control-msg"] }
|
||||||
tor-rtcompat = { version = "0.9.1", path = "../tor-rtcompat" }
|
tor-rtcompat = { version = "0.9.1", path = "../tor-rtcompat" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -8,9 +8,21 @@
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use futures::channel::mpsc;
|
use futures::{
|
||||||
|
channel::{mpsc, oneshot},
|
||||||
|
StreamExt as _,
|
||||||
|
};
|
||||||
|
use tor_cell::relaycell::{
|
||||||
|
hs::est_intro::EstablishIntroDetails,
|
||||||
|
msg::{AnyRelayMsg, IntroEstablished, Introduce2},
|
||||||
|
RelayMsg as _,
|
||||||
|
};
|
||||||
use tor_circmgr::hspool::HsCircPool;
|
use tor_circmgr::hspool::HsCircPool;
|
||||||
use tor_netdir::{NetDirProvider, Relay};
|
use tor_error::{internal, into_internal};
|
||||||
|
use tor_hscrypto::pk::HsIntroPtSessionIdKeypair;
|
||||||
|
use tor_linkspec::CircTarget;
|
||||||
|
use tor_netdir::{NetDir, NetDirProvider, Relay};
|
||||||
|
use tor_proto::circuit::{ConversationInHandler, MetaCellDisposition};
|
||||||
use tor_rtcompat::Runtime;
|
use tor_rtcompat::Runtime;
|
||||||
|
|
||||||
use crate::RendRequest;
|
use crate::RendRequest;
|
||||||
|
@ -36,12 +48,43 @@ impl Drop for IptEstablisher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error from trying to create in introduction point establisher.
|
/// An error from trying to work with an IptEstablisher.
|
||||||
///
|
|
||||||
/// TODO HSS: This is probably too narrow a definition; do something else
|
|
||||||
/// instead.
|
|
||||||
#[derive(Clone, Debug, thiserror::Error)]
|
#[derive(Clone, Debug, thiserror::Error)]
|
||||||
pub(crate) enum IptError {}
|
pub(crate) enum IptError {
|
||||||
|
/// We couldn't get a network directory to use when building circuits.
|
||||||
|
#[error("No network directory available")]
|
||||||
|
NoNetdir(#[source] tor_netdir::Error),
|
||||||
|
|
||||||
|
/// The network directory provider is shutting down without giving us the
|
||||||
|
/// netdir we asked for.
|
||||||
|
#[error("Network directory provider is shutting down")]
|
||||||
|
NetdirProviderShutdown,
|
||||||
|
|
||||||
|
/// We encountered an error while building a circuit to an intro point.
|
||||||
|
#[error("Unable to build circuit to introduction point")]
|
||||||
|
BuildCircuit(#[source] tor_circmgr::Error),
|
||||||
|
|
||||||
|
/// We encountered an error while building and signing our establish_intro
|
||||||
|
/// message.
|
||||||
|
#[error("Unable to construct signed ESTABLISH_INTRO message")]
|
||||||
|
CreateEstablishIntro(#[source] tor_cell::Error),
|
||||||
|
|
||||||
|
/// We encountered an error while sending our establish_intro
|
||||||
|
/// message.
|
||||||
|
#[error("Unable to send an ESTABLISH_INTRO message")]
|
||||||
|
SendEstablishIntro(#[source] tor_proto::Error),
|
||||||
|
|
||||||
|
/// We did not receive an INTRO_ESTABLISHED message like we wanted.
|
||||||
|
#[error("Did not receive INTRO_ESTABLISHED message")]
|
||||||
|
// TODO HSS: I'd like to receive more information here. What happened
|
||||||
|
// instead? But the information might be in the MsgHandler, might be in the
|
||||||
|
// Circuit,...
|
||||||
|
ReceiveAck,
|
||||||
|
|
||||||
|
/// We encountered a programming error.
|
||||||
|
#[error("Internal error")]
|
||||||
|
Bug(#[from] tor_error::Bug),
|
||||||
|
}
|
||||||
|
|
||||||
impl IptEstablisher {
|
impl IptEstablisher {
|
||||||
/// Try to set up, and maintain, an IPT at `Relay`
|
/// Try to set up, and maintain, an IPT at `Relay`
|
||||||
|
@ -112,3 +155,197 @@ pub(crate) struct IptStatus {
|
||||||
/// retired based on having processed too many requests.
|
/// retired based on having processed too many requests.
|
||||||
pub(crate) wants_to_retire: Result<(), IptWantsToRetire>,
|
pub(crate) wants_to_retire: Result<(), IptWantsToRetire>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tor_cell::restricted_msg! {
|
||||||
|
/// An acceptable message to receive from an introduction point.
|
||||||
|
enum IptMsg : RelayMsg {
|
||||||
|
IntroEstablished,
|
||||||
|
Introduce2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try, once, to make a circuit to a single relay and establish an introduction
|
||||||
|
/// point there.
|
||||||
|
///
|
||||||
|
/// Does not retry. Does not time out except via `HsCircPool`.
|
||||||
|
async fn establish_intro_once<R, T>(
|
||||||
|
pool: Arc<HsCircPool<R>>,
|
||||||
|
netdir_provider: Arc<dyn NetDirProvider>,
|
||||||
|
target: T,
|
||||||
|
ipt_sid_keypair: &HsIntroPtSessionIdKeypair,
|
||||||
|
// TODO HSS: Take other extensions. Ideally we would take not only
|
||||||
|
// DoSParams, but a full IntoIterator<Item=EstablishIntroExt> or ExtList.
|
||||||
|
// But both of those types are private in tor-cell. We should consider
|
||||||
|
// whether that's what we want.
|
||||||
|
) -> Result<(), IptError>
|
||||||
|
where
|
||||||
|
R: Runtime,
|
||||||
|
T: CircTarget,
|
||||||
|
{
|
||||||
|
let circuit = {
|
||||||
|
let netdir =
|
||||||
|
wait_for_netdir(netdir_provider.as_ref(), tor_netdir::Timeliness::Timely).await?;
|
||||||
|
let kind = tor_circmgr::hspool::HsCircKind::SvcIntro;
|
||||||
|
pool.get_or_launch_specific(netdir.as_ref(), kind, target)
|
||||||
|
.await
|
||||||
|
.map_err(IptError::BuildCircuit)?
|
||||||
|
// note that netdir is dropped here, to avoid holding on to it any
|
||||||
|
// longer than necessary.
|
||||||
|
};
|
||||||
|
let intro_pt_hop = circuit
|
||||||
|
.last_hop_num()
|
||||||
|
.map_err(into_internal!("Somehow built a circuit with no hops!?"))?;
|
||||||
|
|
||||||
|
let establish_intro = {
|
||||||
|
let ipt_sid_id = ipt_sid_keypair.as_ref().public.into();
|
||||||
|
let details = EstablishIntroDetails::new(ipt_sid_id);
|
||||||
|
let circuit_binding_key = circuit
|
||||||
|
.binding_key(intro_pt_hop)
|
||||||
|
.ok_or(internal!("No binding key for introduction point!?"))?;
|
||||||
|
if false {
|
||||||
|
// TODO HSS: Insert extensions as needed.
|
||||||
|
}
|
||||||
|
let body: Vec<u8> = details
|
||||||
|
.sign_and_encode(ipt_sid_keypair.as_ref(), circuit_binding_key.hs_mac())
|
||||||
|
.map_err(IptError::CreateEstablishIntro)?;
|
||||||
|
|
||||||
|
// TODO HSS: This is ugly, but it is the sensible way to munge the above
|
||||||
|
// body into a format that AnyRelayCell will accept without doing a
|
||||||
|
// redundant parse step.
|
||||||
|
//
|
||||||
|
// One alternative would be allowing start_conversation to take an `impl
|
||||||
|
// RelayMsg` rather than an AnyRelayMsg.
|
||||||
|
//
|
||||||
|
// Or possibly, when we feel like it, we could rename one or more of
|
||||||
|
// these "Unrecognized"s to Unparsed or Uninterpreted. If we do that, however, we'll
|
||||||
|
// potentially face breaking changes up and down our crate stack.
|
||||||
|
AnyRelayMsg::Unrecognized(tor_cell::relaycell::msg::Unrecognized::new(
|
||||||
|
tor_cell::relaycell::RelayCmd::ESTABLISH_INTRO,
|
||||||
|
body,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
let (established_tx, established_rx) = oneshot::channel();
|
||||||
|
let (introduce_tx, introduce_rx) = mpsc::unbounded();
|
||||||
|
|
||||||
|
let handler = IptMsgHandler {
|
||||||
|
established_tx: Some(established_tx),
|
||||||
|
introduce_tx,
|
||||||
|
};
|
||||||
|
let conversation = circuit
|
||||||
|
.start_conversation(Some(establish_intro), handler, intro_pt_hop)
|
||||||
|
.await
|
||||||
|
.map_err(IptError::SendEstablishIntro)?;
|
||||||
|
// At this point, we have `await`ed for the Conversation to exist, so we know
|
||||||
|
// that the message was sent. We have to wait for any actual `established`
|
||||||
|
// message, though.
|
||||||
|
|
||||||
|
let established = established_rx.await.map_err(|_| IptError::ReceiveAck)?;
|
||||||
|
|
||||||
|
// TODO HSS: handle all the extension data in the established field.
|
||||||
|
|
||||||
|
// TODO HSS: Return the introduce_rx stream along with any related types.
|
||||||
|
// Or should we have taken introduce_tx as an argument? (@diziet endorses
|
||||||
|
// the "take it as an argument" idea.)
|
||||||
|
|
||||||
|
// TODO HSS: Return the circuit too, of course.
|
||||||
|
|
||||||
|
// TODO HSS: How shall we know if the other side has closed the circuit? We could wait
|
||||||
|
// for introduce_rx.next() to yield None, but that will only work if we use
|
||||||
|
// one mpsc::Sender per circuit...
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a NetDir from `provider`, waiting until one exists.
|
||||||
|
///
|
||||||
|
/// TODO: perhaps this function would be more generally useful if it were not here?
|
||||||
|
async fn wait_for_netdir(
|
||||||
|
provider: &dyn NetDirProvider,
|
||||||
|
timeliness: tor_netdir::Timeliness,
|
||||||
|
) -> Result<Arc<NetDir>, IptError> {
|
||||||
|
if let Ok(nd) = provider.netdir(timeliness) {
|
||||||
|
return Ok(nd);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stream = provider.events();
|
||||||
|
loop {
|
||||||
|
// We need to retry `provider.netdir()` before waiting for any stream events, to
|
||||||
|
// avoid deadlock.
|
||||||
|
//
|
||||||
|
// TODO HSS: propagate _some_ possible errors here.
|
||||||
|
if let Ok(nd) = provider.netdir(timeliness) {
|
||||||
|
return Ok(nd);
|
||||||
|
}
|
||||||
|
match stream.next().await {
|
||||||
|
Some(_) => {}
|
||||||
|
None => {
|
||||||
|
return Err(IptError::NetdirProviderShutdown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MsgHandler type to implement a conversation with an introduction point.
|
||||||
|
///
|
||||||
|
/// This, like all MsgHandlers, is installed at the circuit's reactor, and used
|
||||||
|
/// to handle otherwise unrecognized message types.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct IptMsgHandler {
|
||||||
|
/// A oneshot sender used to report our IntroEstablished message.
|
||||||
|
///
|
||||||
|
/// If this is None, then we already sent an IntroEstablished and we shouldn't
|
||||||
|
/// send any more.
|
||||||
|
established_tx: Option<oneshot::Sender<IntroEstablished>>,
|
||||||
|
/// A channel used to report Introduce2 messages.
|
||||||
|
//
|
||||||
|
// TODO HSS: I don't like having this be unbounded, but `handle_msg` can't
|
||||||
|
// block. On the other hand maybe we can just discard excessive introduce2
|
||||||
|
// messages if we're under high load? I think that's what C tor does,
|
||||||
|
// especially when under DoS conditions.
|
||||||
|
//
|
||||||
|
// See discussion at
|
||||||
|
// https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/1465#note_2928349
|
||||||
|
introduce_tx: mpsc::UnboundedSender<Introduce2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tor_proto::circuit::MsgHandler for IptMsgHandler {
|
||||||
|
fn handle_msg(
|
||||||
|
&mut self,
|
||||||
|
conversation: ConversationInHandler<'_, '_, '_>,
|
||||||
|
any_msg: AnyRelayMsg,
|
||||||
|
) -> tor_proto::Result<MetaCellDisposition> {
|
||||||
|
// TODO HSS: Implement rate-limiting.
|
||||||
|
//
|
||||||
|
// TODO HSS: Is CircProto right or should this be a new error type?
|
||||||
|
let msg: IptMsg = any_msg.try_into().map_err(|m: AnyRelayMsg| {
|
||||||
|
tor_proto::Error::CircProto(format!("Invalid message type {}", m.cmd()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if match msg {
|
||||||
|
IptMsg::IntroEstablished(established) => match self.established_tx.take() {
|
||||||
|
Some(tx) => tx.send(established).map_err(|_| ()),
|
||||||
|
None => {
|
||||||
|
return Err(tor_proto::Error::CircProto(
|
||||||
|
"Received a redundant INTRO_ESTABLISHED".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
IptMsg::Introduce2(introduce2) => {
|
||||||
|
if self.established_tx.is_some() {
|
||||||
|
return Err(tor_proto::Error::CircProto(
|
||||||
|
"Received an INTRODUCE2 message before INTRO_ESTABLISHED".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
self.introduce_tx.unbounded_send(introduce2).map_err(|_| ())
|
||||||
|
}
|
||||||
|
} == Err(())
|
||||||
|
{
|
||||||
|
// If the above return an error, we failed to send. That means that
|
||||||
|
// we need to close the circuit, since nobody is listening on the
|
||||||
|
// other end of the tx.
|
||||||
|
return Ok(MetaCellDisposition::CloseCirc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(MetaCellDisposition::Consumed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
ADDED: traits module.
|
|
@ -43,4 +43,6 @@
|
||||||
pub mod cipher;
|
pub mod cipher;
|
||||||
pub mod d;
|
pub mod d;
|
||||||
pub mod pk;
|
pub mod pk;
|
||||||
|
#[cfg(feature = "hsv3-service")] // TODO HSS: Remove this feature gate
|
||||||
|
pub mod traits;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
//! Cryptographic traits for general use throughout Arti.
|
||||||
|
|
||||||
|
use subtle::Choice;
|
||||||
|
|
||||||
|
/// A simple trait to describe a keyed message authentication code.
|
||||||
|
///
|
||||||
|
/// Unlike RustCrypto's
|
||||||
|
/// [`crypto_mac::Mac`](https://docs.rs/crypto-mac/latest/crypto_mac/trait.Mac.html),
|
||||||
|
/// this trait does not support incremental processing.
|
||||||
|
pub trait ShortMac<const MAC_LEN: usize> {
|
||||||
|
/// Calculate a message authentication code for `input` using this key.
|
||||||
|
fn mac(&self, input: &[u8]) -> crate::util::ct::CtByteArray<MAC_LEN>;
|
||||||
|
|
||||||
|
/// Check whether `mac` is a valid message authentication code for `input`
|
||||||
|
/// using this key.
|
||||||
|
fn validate(&self, input: &[u8], mac: &[u8; MAC_LEN]) -> Choice;
|
||||||
|
}
|
|
@ -14,3 +14,6 @@ ADDED: `ClientCirc::start_conversation()` to eventually replace
|
||||||
BREAKING: `ClientCirc::allow_stream_requests` is now async
|
BREAKING: `ClientCirc::allow_stream_requests` is now async
|
||||||
BREAKING: `IncomingStream::discard` now takes `mut self` instead of `self` and
|
BREAKING: `IncomingStream::discard` now takes `mut self` instead of `self` and
|
||||||
returns a `Result<(), Bug>`
|
returns a `Result<(), Bug>`
|
||||||
|
ADDED: `ClientCirc::binding_key`
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -57,12 +57,14 @@ use crate::circuit::reactor::{
|
||||||
CircuitHandshake, CtrlMsg, Reactor, RECV_WINDOW_INIT, STREAM_READER_BUFFER,
|
CircuitHandshake, CtrlMsg, Reactor, RECV_WINDOW_INIT, STREAM_READER_BUFFER,
|
||||||
};
|
};
|
||||||
pub use crate::circuit::unique_id::UniqId;
|
pub use crate::circuit::unique_id::UniqId;
|
||||||
|
pub use crate::crypto::binding::CircuitBinding;
|
||||||
use crate::crypto::cell::HopNum;
|
use crate::crypto::cell::HopNum;
|
||||||
use crate::stream::{
|
use crate::stream::{
|
||||||
AnyCmdChecker, DataCmdChecker, DataStream, ResolveCmdChecker, ResolveStream, StreamParameters,
|
AnyCmdChecker, DataCmdChecker, DataStream, ResolveCmdChecker, ResolveStream, StreamParameters,
|
||||||
StreamReader,
|
StreamReader,
|
||||||
};
|
};
|
||||||
use crate::{Error, ResolveError, Result};
|
use crate::{Error, ResolveError, Result};
|
||||||
|
use educe::Educe;
|
||||||
use tor_cell::{
|
use tor_cell::{
|
||||||
chancell::{self, msg::AnyChanMsg, CircId},
|
chancell::{self, msg::AnyChanMsg, CircId},
|
||||||
relaycell::msg::{AnyRelayMsg, Begin, Resolve, Resolved, ResolvedVal},
|
relaycell::msg::{AnyRelayMsg, Begin, Resolve, Resolved, ResolvedVal},
|
||||||
|
@ -171,7 +173,8 @@ pub struct ClientCirc {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable state shared by [`ClientCirc`] and [`Reactor`].
|
/// Mutable state shared by [`ClientCirc`] and [`Reactor`].
|
||||||
#[derive(Debug)]
|
#[derive(Educe)]
|
||||||
|
#[educe(Debug)]
|
||||||
struct MutableState {
|
struct MutableState {
|
||||||
/// Information about this circuit's path.
|
/// Information about this circuit's path.
|
||||||
///
|
///
|
||||||
|
@ -179,6 +182,16 @@ struct MutableState {
|
||||||
/// client code; when we need to add a hop (which is less frequent) we use
|
/// client code; when we need to add a hop (which is less frequent) we use
|
||||||
/// [`Arc::make_mut()`].
|
/// [`Arc::make_mut()`].
|
||||||
path: Arc<path::Path>,
|
path: Arc<path::Path>,
|
||||||
|
|
||||||
|
/// Circuit binding keys [q.v.][`CircuitBinding`] information for each hop
|
||||||
|
/// in the circuit's path.
|
||||||
|
///
|
||||||
|
/// NOTE: Right now, there is a `CircuitBinding` for every hop. There's a
|
||||||
|
/// fair chance that this will change in the future, and I don't want other
|
||||||
|
/// code to assume that a `CircuitBinding` _must_ exist, so I'm making this
|
||||||
|
/// an `Option`.
|
||||||
|
#[educe(Debug(ignore))]
|
||||||
|
binding: Vec<Option<CircuitBinding>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A ClientCirc that needs to send a create cell and receive a created* cell.
|
/// A ClientCirc that needs to send a create cell and receive a created* cell.
|
||||||
|
@ -342,6 +355,25 @@ impl ClientCirc {
|
||||||
&self.channel
|
&self.channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the cryptographic material used to prove knowledge of a shared
|
||||||
|
/// secret with with `hop`.
|
||||||
|
///
|
||||||
|
/// See [`CircuitBinding`] for more information on how this is used.
|
||||||
|
///
|
||||||
|
/// Return None if we have no circuit binding information for the hop, or if
|
||||||
|
/// the hop does not exist.
|
||||||
|
pub fn binding_key(&self, hop: HopNum) -> Option<CircuitBinding> {
|
||||||
|
self.mutable
|
||||||
|
.lock()
|
||||||
|
.expect("poisoned lock")
|
||||||
|
.binding
|
||||||
|
.get::<usize>(hop.into())
|
||||||
|
.cloned()
|
||||||
|
.flatten()
|
||||||
|
// NOTE: I'm not thrilled to have to copy this information, but we use
|
||||||
|
// it very rarely, so it's not _that_ bad IMO.
|
||||||
|
}
|
||||||
|
|
||||||
/// Start an ad-hoc protocol exchange to the specified hop on this circuit
|
/// Start an ad-hoc protocol exchange to the specified hop on this circuit
|
||||||
///
|
///
|
||||||
/// To use this:
|
/// To use this:
|
||||||
|
@ -591,11 +623,13 @@ impl ClientCirc {
|
||||||
seed: impl handshake::KeyGenerator,
|
seed: impl handshake::KeyGenerator,
|
||||||
params: CircParameters,
|
params: CircParameters,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (outbound, inbound) = protocol.construct_layers(role, seed)?;
|
use self::handshake::BoxedClientLayer;
|
||||||
|
|
||||||
|
let BoxedClientLayer { fwd, back, binding } = protocol.construct_layers(role, seed)?;
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
let message = CtrlMsg::ExtendVirtual {
|
let message = CtrlMsg::ExtendVirtual {
|
||||||
cell_crypto: (outbound, inbound),
|
cell_crypto: (fwd, back, binding),
|
||||||
params,
|
params,
|
||||||
done: tx,
|
done: tx,
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
// that can wait IMO until we have a second circuit creation mechanism for use
|
// that can wait IMO until we have a second circuit creation mechanism for use
|
||||||
// with onion services.
|
// with onion services.
|
||||||
|
|
||||||
|
use crate::crypto::binding::CircuitBinding;
|
||||||
use crate::crypto::cell::{
|
use crate::crypto::cell::{
|
||||||
ClientLayer, CryptInit, InboundClientLayer, OutboundClientLayer, Tor1Hsv3RelayCrypto,
|
ClientLayer, CryptInit, InboundClientLayer, OutboundClientLayer, Tor1Hsv3RelayCrypto,
|
||||||
};
|
};
|
||||||
|
@ -41,6 +42,17 @@ pub enum HandshakeRole {
|
||||||
Responder,
|
Responder,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A set of type-erased cryptographic layers to use for a single hop at a
|
||||||
|
/// client.
|
||||||
|
pub(crate) struct BoxedClientLayer {
|
||||||
|
/// The outbound cryptographic layer to use for this hop
|
||||||
|
pub(crate) fwd: Box<dyn OutboundClientLayer + Send>,
|
||||||
|
/// The inbound cryptogarphic layer to use for this hop
|
||||||
|
pub(crate) back: Box<dyn InboundClientLayer + Send>,
|
||||||
|
/// A circuit binding key for this hop.
|
||||||
|
pub(crate) binding: Option<CircuitBinding>,
|
||||||
|
}
|
||||||
|
|
||||||
impl RelayProtocol {
|
impl RelayProtocol {
|
||||||
/// Construct the cell-crypto layers that are needed for a given set of
|
/// Construct the cell-crypto layers that are needed for a given set of
|
||||||
/// circuit hop parameters.
|
/// circuit hop parameters.
|
||||||
|
@ -48,21 +60,22 @@ impl RelayProtocol {
|
||||||
self,
|
self,
|
||||||
role: HandshakeRole,
|
role: HandshakeRole,
|
||||||
keygen: impl KeyGenerator,
|
keygen: impl KeyGenerator,
|
||||||
) -> Result<(
|
) -> Result<BoxedClientLayer> {
|
||||||
Box<dyn OutboundClientLayer + Send>,
|
|
||||||
Box<dyn InboundClientLayer + Send>,
|
|
||||||
)> {
|
|
||||||
match self {
|
match self {
|
||||||
RelayProtocol::HsV3 => {
|
RelayProtocol::HsV3 => {
|
||||||
let seed_needed = Tor1Hsv3RelayCrypto::seed_len();
|
let seed_needed = Tor1Hsv3RelayCrypto::seed_len();
|
||||||
let seed = keygen.expand(seed_needed)?;
|
let seed = keygen.expand(seed_needed)?;
|
||||||
let layer = Tor1Hsv3RelayCrypto::initialize(&seed)?;
|
let layer = Tor1Hsv3RelayCrypto::initialize(&seed)?;
|
||||||
let (fwd, back) = layer.split();
|
let (fwd, back, binding) = layer.split();
|
||||||
let (fwd, back) = match role {
|
let (fwd, back) = match role {
|
||||||
HandshakeRole::Initiator => (fwd, back),
|
HandshakeRole::Initiator => (fwd, back),
|
||||||
HandshakeRole::Responder => (back, fwd),
|
HandshakeRole::Responder => (back, fwd),
|
||||||
};
|
};
|
||||||
Ok((Box::new(fwd), Box::new(back)))
|
Ok(BoxedClientLayer {
|
||||||
|
fwd: Box::new(fwd),
|
||||||
|
back: Box::new(back),
|
||||||
|
binding: Some(binding),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ use crate::circuit::unique_id::UniqId;
|
||||||
use crate::circuit::{
|
use crate::circuit::{
|
||||||
sendme, streammap, CircParameters, Create2Wrap, CreateFastWrap, CreateHandshakeWrap,
|
sendme, streammap, CircParameters, Create2Wrap, CreateFastWrap, CreateHandshakeWrap,
|
||||||
};
|
};
|
||||||
|
use crate::crypto::binding::CircuitBinding;
|
||||||
use crate::crypto::cell::{
|
use crate::crypto::cell::{
|
||||||
ClientLayer, CryptInit, HopNum, InboundClientCrypt, InboundClientLayer, OutboundClientCrypt,
|
ClientLayer, CryptInit, HopNum, InboundClientCrypt, InboundClientLayer, OutboundClientCrypt,
|
||||||
OutboundClientLayer, RelayCellBody, Tor1RelayCrypto,
|
OutboundClientLayer, RelayCellBody, Tor1RelayCrypto,
|
||||||
|
@ -134,6 +135,7 @@ pub(super) enum CtrlMsg {
|
||||||
cell_crypto: (
|
cell_crypto: (
|
||||||
Box<dyn OutboundClientLayer + Send>,
|
Box<dyn OutboundClientLayer + Send>,
|
||||||
Box<dyn InboundClientLayer + Send>,
|
Box<dyn InboundClientLayer + Send>,
|
||||||
|
Option<CircuitBinding>,
|
||||||
),
|
),
|
||||||
/// A set of parameters used to configure this hop.
|
/// A set of parameters used to configure this hop.
|
||||||
params: CircParameters,
|
params: CircParameters,
|
||||||
|
@ -490,11 +492,12 @@ where
|
||||||
debug!("{}: Handshake complete; circuit extended.", self.unique_id);
|
debug!("{}: Handshake complete; circuit extended.", self.unique_id);
|
||||||
|
|
||||||
// If we get here, it succeeded. Add a new hop to the circuit.
|
// If we get here, it succeeded. Add a new hop to the circuit.
|
||||||
let (layer_fwd, layer_back) = layer.split();
|
let (layer_fwd, layer_back, binding) = layer.split();
|
||||||
reactor.add_hop(
|
reactor.add_hop(
|
||||||
path::HopDetail::Relay(self.peer_id.clone()),
|
path::HopDetail::Relay(self.peer_id.clone()),
|
||||||
Box::new(layer_fwd),
|
Box::new(layer_fwd),
|
||||||
Box::new(layer_back),
|
Box::new(layer_back),
|
||||||
|
Some(binding),
|
||||||
&self.params,
|
&self.params,
|
||||||
);
|
);
|
||||||
Ok(MetaCellDisposition::ConversationFinished)
|
Ok(MetaCellDisposition::ConversationFinished)
|
||||||
|
@ -640,7 +643,8 @@ impl Reactor {
|
||||||
let crypto_out = OutboundClientCrypt::new();
|
let crypto_out = OutboundClientCrypt::new();
|
||||||
let (control_tx, control_rx) = mpsc::unbounded();
|
let (control_tx, control_rx) = mpsc::unbounded();
|
||||||
let path = Arc::new(path::Path::default());
|
let path = Arc::new(path::Path::default());
|
||||||
let mutable = Arc::new(Mutex::new(MutableState { path }));
|
let binding = Vec::new();
|
||||||
|
let mutable = Arc::new(Mutex::new(MutableState { path, binding }));
|
||||||
|
|
||||||
let (reactor_closed_tx, reactor_closed_rx) = oneshot::channel();
|
let (reactor_closed_tx, reactor_closed_rx) = oneshot::channel();
|
||||||
|
|
||||||
|
@ -937,7 +941,14 @@ impl Reactor {
|
||||||
|
|
||||||
let fwd = Box::new(DummyCrypto::new(fwd_lasthop));
|
let fwd = Box::new(DummyCrypto::new(fwd_lasthop));
|
||||||
let rev = Box::new(DummyCrypto::new(rev_lasthop));
|
let rev = Box::new(DummyCrypto::new(rev_lasthop));
|
||||||
self.add_hop(path::HopDetail::Relay(dummy_peer_id), fwd, rev, params);
|
let binding = None;
|
||||||
|
self.add_hop(
|
||||||
|
path::HopDetail::Relay(dummy_peer_id),
|
||||||
|
fwd,
|
||||||
|
rev,
|
||||||
|
binding,
|
||||||
|
params,
|
||||||
|
);
|
||||||
let _ = done.send(Ok(()));
|
let _ = done.send(Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -991,13 +1002,14 @@ impl Reactor {
|
||||||
|
|
||||||
debug!("{}: Handshake complete; circuit created.", self.unique_id);
|
debug!("{}: Handshake complete; circuit created.", self.unique_id);
|
||||||
|
|
||||||
let (layer_fwd, layer_back) = layer.split();
|
let (layer_fwd, layer_back, binding) = layer.split();
|
||||||
let peer_id = self.channel.target().clone();
|
let peer_id = self.channel.target().clone();
|
||||||
|
|
||||||
self.add_hop(
|
self.add_hop(
|
||||||
path::HopDetail::Relay(peer_id),
|
path::HopDetail::Relay(peer_id),
|
||||||
Box::new(layer_fwd),
|
Box::new(layer_fwd),
|
||||||
Box::new(layer_back),
|
Box::new(layer_back),
|
||||||
|
Some(binding),
|
||||||
params,
|
params,
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1062,6 +1074,7 @@ impl Reactor {
|
||||||
peer_id: path::HopDetail,
|
peer_id: path::HopDetail,
|
||||||
fwd: Box<dyn OutboundClientLayer + 'static + Send>,
|
fwd: Box<dyn OutboundClientLayer + 'static + Send>,
|
||||||
rev: Box<dyn InboundClientLayer + 'static + Send>,
|
rev: Box<dyn InboundClientLayer + 'static + Send>,
|
||||||
|
binding: Option<CircuitBinding>,
|
||||||
params: &CircParameters,
|
params: &CircParameters,
|
||||||
) {
|
) {
|
||||||
let hop = crate::circuit::reactor::CircHop::new(params.initial_send_window());
|
let hop = crate::circuit::reactor::CircHop::new(params.initial_send_window());
|
||||||
|
@ -1070,6 +1083,7 @@ impl Reactor {
|
||||||
self.crypto_out.add_layer(fwd);
|
self.crypto_out.add_layer(fwd);
|
||||||
let mut mutable = self.mutable.lock().expect("poisoned lock");
|
let mut mutable = self.mutable.lock().expect("poisoned lock");
|
||||||
Arc::make_mut(&mut mutable.path).push_hop(peer_id);
|
Arc::make_mut(&mut mutable.path).push_hop(peer_id);
|
||||||
|
mutable.binding.push(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a RELAY cell on this circuit with stream ID 0.
|
/// Handle a RELAY cell on this circuit with stream ID 0.
|
||||||
|
@ -1382,13 +1396,13 @@ impl Reactor {
|
||||||
params,
|
params,
|
||||||
done,
|
done,
|
||||||
} => {
|
} => {
|
||||||
let (outbound, inbound) = cell_crypto;
|
let (outbound, inbound, binding) = cell_crypto;
|
||||||
|
|
||||||
// TODO HS: Perhaps this should describe the onion service, or
|
// TODO HS: Perhaps this should describe the onion service, or
|
||||||
// describe why the virtual hop was added, or something?
|
// describe why the virtual hop was added, or something?
|
||||||
let peer_id = path::HopDetail::Virtual;
|
let peer_id = path::HopDetail::Virtual;
|
||||||
|
|
||||||
self.add_hop(peer_id, outbound, inbound, ¶ms);
|
self.add_hop(peer_id, outbound, inbound, binding, ¶ms);
|
||||||
let _ = done.send(Ok(()));
|
let _ = done.send(Ok(()));
|
||||||
}
|
}
|
||||||
CtrlMsg::BeginStream {
|
CtrlMsg::BeginStream {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//! * `handshake` implements the ntor handshake.
|
//! * `handshake` implements the ntor handshake.
|
||||||
//! * `ll` provides building blocks for other parts of the protocol.
|
//! * `ll` provides building blocks for other parts of the protocol.
|
||||||
|
|
||||||
|
pub(crate) mod binding;
|
||||||
pub(crate) mod cell;
|
pub(crate) mod cell;
|
||||||
pub(crate) mod handshake;
|
pub(crate) mod handshake;
|
||||||
pub(crate) mod ll;
|
pub(crate) mod ll;
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
//! Types related to binding messages to specific circuits
|
||||||
|
|
||||||
|
#[cfg(feature = "hs-service")]
|
||||||
|
use tor_hscrypto::ops::HsMacKey;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
/// Number of bytes of circuit binding material negotiated per circuit hop.
|
||||||
|
pub(crate) const CIRC_BINDING_LEN: usize = 20;
|
||||||
|
|
||||||
|
/// Cryptographic information used to bind a message to a specific circuit.
|
||||||
|
///
|
||||||
|
/// This information is used in some of our protocols (currently only the onion
|
||||||
|
/// services protocol) to prove that a given message was referring to a specific
|
||||||
|
/// hop on a specific circuit, and was not replayed from another circuit.
|
||||||
|
///
|
||||||
|
/// In `tor-spec` and `rend-spec`, this value is called `KH`.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CircuitBinding(
|
||||||
|
// We use a Box here to avoid moves that would bypass the zeroize-on-drop
|
||||||
|
// semantics.
|
||||||
|
//
|
||||||
|
// (This is not super-critical, since the impact of leaking one of these
|
||||||
|
// keys is slight, but it's best not to leak them at all.)
|
||||||
|
Box<Zeroizing<[u8; CIRC_BINDING_LEN]>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl From<[u8; CIRC_BINDING_LEN]> for CircuitBinding {
|
||||||
|
fn from(value: [u8; CIRC_BINDING_LEN]) -> Self {
|
||||||
|
Self(Box::new(Zeroizing::new(value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CircuitBinding {
|
||||||
|
/// Return a view of this key suitable for computing the MAC function used
|
||||||
|
/// to authenticate onion services' ESTABLISH_INTRODUCE messages.
|
||||||
|
///
|
||||||
|
/// Note that this is not a general-purpose MAC; please avoid adding new
|
||||||
|
/// users of it. See notes on [`hs_mac`](tor_hscrypto::ops::hs_mac) for
|
||||||
|
/// more information.
|
||||||
|
#[cfg(feature = "hs-service")]
|
||||||
|
pub fn hs_mac(&self) -> HsMacKey<'_> {
|
||||||
|
HsMacKey::from(self.dangerously_into_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a view of this key as a byte-slice.
|
||||||
|
///
|
||||||
|
/// This is potentially dangerous, since we don't want to expose this
|
||||||
|
/// information: We only want to use it as a MAC key.
|
||||||
|
fn dangerously_into_bytes(&self) -> &[u8] {
|
||||||
|
&(**self.0)[..]
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,6 +36,8 @@ use tor_error::internal;
|
||||||
|
|
||||||
use generic_array::GenericArray;
|
use generic_array::GenericArray;
|
||||||
|
|
||||||
|
use super::binding::CircuitBinding;
|
||||||
|
|
||||||
/// Type for the body of a relay cell.
|
/// Type for the body of a relay cell.
|
||||||
#[derive(Clone, derive_more::From, derive_more::Into)]
|
#[derive(Clone, derive_more::From, derive_more::Into)]
|
||||||
pub(crate) struct RelayCellBody(BoxedCellBody);
|
pub(crate) struct RelayCellBody(BoxedCellBody);
|
||||||
|
@ -75,8 +77,8 @@ where
|
||||||
B: InboundClientLayer,
|
B: InboundClientLayer,
|
||||||
{
|
{
|
||||||
/// Consume this ClientLayer and return a paired forward and reverse
|
/// Consume this ClientLayer and return a paired forward and reverse
|
||||||
/// crypto layer.
|
/// crypto layer, and a [`CircuitBinding`] object
|
||||||
fn split(self) -> (F, B);
|
fn split(self) -> (F, B, CircuitBinding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a relay's view of the crypto state on a given circuit.
|
/// Represents a relay's view of the crypto state on a given circuit.
|
||||||
|
@ -259,6 +261,8 @@ pub(crate) type Tor1Hsv3RelayCrypto =
|
||||||
/// I am calling this design `tor1`; it does not have a generally recognized
|
/// I am calling this design `tor1`; it does not have a generally recognized
|
||||||
/// name.
|
/// name.
|
||||||
pub(crate) mod tor1 {
|
pub(crate) mod tor1 {
|
||||||
|
use crate::crypto::binding::CIRC_BINDING_LEN;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use cipher::{KeyIvInit, StreamCipher};
|
use cipher::{KeyIvInit, StreamCipher};
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
|
@ -298,11 +302,13 @@ pub(crate) mod tor1 {
|
||||||
fwd: CryptState<SC, D>,
|
fwd: CryptState<SC, D>,
|
||||||
/// State for en/decrypting cells sent towards the client.
|
/// State for en/decrypting cells sent towards the client.
|
||||||
back: CryptState<SC, D>,
|
back: CryptState<SC, D>,
|
||||||
|
/// A circuit binding key.
|
||||||
|
binding: CircuitBinding,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SC: StreamCipher + KeyIvInit, D: Digest + Clone> CryptInit for CryptStatePair<SC, D> {
|
impl<SC: StreamCipher + KeyIvInit, D: Digest + Clone> CryptInit for CryptStatePair<SC, D> {
|
||||||
fn seed_len() -> usize {
|
fn seed_len() -> usize {
|
||||||
SC::KeySize::to_usize() * 2 + D::OutputSize::to_usize() * 2
|
SC::KeySize::to_usize() * 2 + D::OutputSize::to_usize() * 2 + CIRC_BINDING_LEN
|
||||||
}
|
}
|
||||||
fn initialize(seed: &[u8]) -> Result<Self> {
|
fn initialize(seed: &[u8]) -> Result<Self> {
|
||||||
// This corresponds to the use of the KDF algorithm as described in
|
// This corresponds to the use of the KDF algorithm as described in
|
||||||
|
@ -319,6 +325,10 @@ pub(crate) mod tor1 {
|
||||||
let bdinit = &seed[dlen..dlen * 2]; // This is Db in the spec.
|
let bdinit = &seed[dlen..dlen * 2]; // This is Db in the spec.
|
||||||
let fckey = &seed[dlen * 2..dlen * 2 + keylen]; // This is Kf in the spec.
|
let fckey = &seed[dlen * 2..dlen * 2 + keylen]; // This is Kf in the spec.
|
||||||
let bckey = &seed[dlen * 2 + keylen..dlen * 2 + keylen * 2]; // this is Kb in the spec.
|
let bckey = &seed[dlen * 2 + keylen..dlen * 2 + keylen * 2]; // this is Kb in the spec.
|
||||||
|
let binding_key: &[u8; CIRC_BINDING_LEN] = &seed
|
||||||
|
[dlen * 2 + keylen * 2..dlen * 2 + keylen * 2 + CIRC_BINDING_LEN]
|
||||||
|
.try_into()
|
||||||
|
.expect("Unable to convert a 20-byte slice to a 20-byte array!?");
|
||||||
let fwd = CryptState {
|
let fwd = CryptState {
|
||||||
cipher: SC::new(fckey.try_into().expect("Wrong length"), &Default::default()),
|
cipher: SC::new(fckey.try_into().expect("Wrong length"), &Default::default()),
|
||||||
digest: D::new().chain_update(fdinit),
|
digest: D::new().chain_update(fdinit),
|
||||||
|
@ -329,7 +339,8 @@ pub(crate) mod tor1 {
|
||||||
digest: D::new().chain_update(bdinit),
|
digest: D::new().chain_update(bdinit),
|
||||||
last_digest_val: GenericArray::default(),
|
last_digest_val: GenericArray::default(),
|
||||||
};
|
};
|
||||||
Ok(CryptStatePair { fwd, back })
|
let binding = CircuitBinding::from(*binding_key);
|
||||||
|
Ok(CryptStatePair { fwd, back, binding })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,8 +349,8 @@ pub(crate) mod tor1 {
|
||||||
SC: StreamCipher,
|
SC: StreamCipher,
|
||||||
D: Digest + Clone,
|
D: Digest + Clone,
|
||||||
{
|
{
|
||||||
fn split(self) -> (CryptState<SC, D>, CryptState<SC, D>) {
|
fn split(self) -> (CryptState<SC, D>, CryptState<SC, D>, CircuitBinding) {
|
||||||
(self.fwd, self.back)
|
(self.fwd, self.back, self.binding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,7 +495,7 @@ mod test {
|
||||||
cc_in: &mut InboundClientCrypt,
|
cc_in: &mut InboundClientCrypt,
|
||||||
pair: Tor1RelayCrypto,
|
pair: Tor1RelayCrypto,
|
||||||
) {
|
) {
|
||||||
let (outbound, inbound) = pair.split();
|
let (outbound, inbound, _) = pair.split();
|
||||||
cc_out.add_layer(Box::new(outbound));
|
cc_out.add_layer(Box::new(outbound));
|
||||||
cc_in.add_layer(Box::new(inbound));
|
cc_in.add_layer(Box::new(inbound));
|
||||||
}
|
}
|
||||||
|
@ -573,12 +584,13 @@ mod test {
|
||||||
use digest::XofReader;
|
use digest::XofReader;
|
||||||
use digest::{ExtendableOutput, Update};
|
use digest::{ExtendableOutput, Update};
|
||||||
|
|
||||||
const K1: &[u8; 72] =
|
// (The ....s at the end here are the KH ca)
|
||||||
b" 'My public key is in this signed x509 object', said Tom assertively.";
|
const K1: &[u8; 92] =
|
||||||
const K2: &[u8; 72] =
|
b" 'My public key is in this signed x509 object', said Tom assertively. (N-PREG-VIRYL)";
|
||||||
b"'Let's chart the pedal phlanges in the tomb', said Tom cryptographically";
|
const K2: &[u8; 92] =
|
||||||
const K3: &[u8; 72] =
|
b"'Let's chart the pedal phlanges in the tomb', said Tom cryptographically. (PELCG-GBR-TENCU)";
|
||||||
b" 'Segmentation fault bugs don't _just happen_', said Tom seethingly.";
|
const K3: &[u8; 92] =
|
||||||
|
b" 'Segmentation fault bugs don't _just happen_', said Tom seethingly. (P-GUVAT-YL)";
|
||||||
|
|
||||||
const SEED: &[u8;108] = b"'You mean to tell me that there's a version of Sha-3 with no limit on the output length?', said Tom shakily.";
|
const SEED: &[u8;108] = b"'You mean to tell me that there's a version of Sha-3 with no limit on the output length?', said Tom shakily.";
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue