pub(crate) mod create_handler;
pub(crate) mod handshake;
pub(crate) mod initiator;
pub(crate) mod responder;
pub use responder::MaybeVerifiableRelayResponderChannel;
use digest::Digest;
use futures::{AsyncRead, AsyncWrite};
use rand::Rng;
use safelog::Sensitive;
use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use std::time::UNIX_EPOCH;
use tor_cell::chancell::msg;
use tor_cert::EncodedEd25519Cert;
use tor_cert::rsa::EncodedRsaCrosscert;
use tor_cert::x509::TlsKeyAndCert;
use tor_error::internal;
use tor_linkspec::{HasRelayIds, OwnedChanTarget, RelayIdRef, RelayIdType};
use tor_llcrypto as ll;
use tor_llcrypto::pk::{
ed25519::{Ed25519Identity, Ed25519SigningKey},
rsa,
rsa::RsaIdentity,
};
use tor_relay_crypto::pk::RelayLinkSigningKeypair;
use tor_rtcompat::{CertifiedConn, CoarseTimeProvider, SleepProvider, StreamOps};
use crate::channel::handshake::VerifiedChannel;
use crate::channel::{ClogDigest, SlogDigest};
use crate::peer::PeerAddr;
use crate::relay::CreateRequestHandler;
use crate::relay::channel::handshake::{AUTHTYPE_ED25519_SHA256_RFC5705, RelayResponderHandshake};
use crate::{Error, Result, channel::RelayInitiatorHandshake, memquota::ChannelAccount};
pub(crate) static LINK_AUTH: &[u16] = &[AUTHTYPE_ED25519_SHA256_RFC5705];
pub struct RelayChannelAuthMaterial {
pub(crate) rsa_id_der_digest: [u8; 32],
pub(crate) rsa_id: RsaIdentity,
pub(crate) ed_id: Ed25519Identity,
pub(crate) link_sign_kp: RelayLinkSigningKeypair,
pub(crate) cert_id_sign_ed: EncodedEd25519Cert,
pub(crate) cert_sign_tls_ed: EncodedEd25519Cert,
pub(crate) cert_sign_link_auth_ed: EncodedEd25519Cert,
pub(crate) cert_id_x509_rsa: Vec<u8>,
pub(crate) cert_id_rsa: EncodedRsaCrosscert,
pub(crate) tls_key_and_cert: TlsKeyAndCert,
}
impl RelayChannelAuthMaterial {
#[allow(clippy::too_many_arguments)] pub fn new(
rsa_id_pk: &rsa::PublicKey,
ed_id: Ed25519Identity,
link_sign_kp: RelayLinkSigningKeypair,
cert_id_sign_ed: EncodedEd25519Cert,
cert_sign_tls_ed: EncodedEd25519Cert,
cert_sign_link_auth_ed: EncodedEd25519Cert,
cert_id_x509_rsa: Vec<u8>,
cert_id_rsa: EncodedRsaCrosscert,
tls_key_and_cert: TlsKeyAndCert,
) -> Self {
Self {
rsa_id_der_digest: ll::d::Sha256::digest(rsa_id_pk.to_der()).into(),
rsa_id: rsa_id_pk.to_rsa_identity(),
ed_id,
link_sign_kp,
cert_id_sign_ed,
cert_sign_tls_ed,
cert_sign_link_auth_ed,
cert_id_x509_rsa,
cert_id_rsa,
tls_key_and_cert,
}
}
pub fn tls_key_and_cert(&self) -> &TlsKeyAndCert {
&self.tls_key_and_cert
}
pub(crate) fn ed_id_bytes(&self) -> [u8; 32] {
self.ed_id.into()
}
}
impl HasRelayIds for RelayChannelAuthMaterial {
fn identity(&self, key_type: RelayIdType) -> Option<RelayIdRef<'_>> {
match key_type {
RelayIdType::Ed25519 => Some(RelayIdRef::from(&self.ed_id)),
RelayIdType::Rsa => Some(RelayIdRef::from(&self.rsa_id)),
_ => None, }
}
}
#[derive(Default)]
#[non_exhaustive]
pub struct RelayChannelBuilder;
impl RelayChannelBuilder {
pub fn new() -> Self {
Self::default()
}
#[allow(clippy::too_many_arguments)] pub fn launch<T, S>(
self,
tls: T,
sleep_prov: S,
auth_material: Arc<RelayChannelAuthMaterial>,
my_addrs: Vec<SocketAddr>,
peer_target: &OwnedChanTarget,
memquota: ChannelAccount,
create_request_handler: Arc<CreateRequestHandler>,
) -> RelayInitiatorHandshake<T, S>
where
T: AsyncRead + AsyncWrite + CertifiedConn + StreamOps + Send + Unpin + 'static,
S: CoarseTimeProvider + SleepProvider,
{
RelayInitiatorHandshake::new(
tls,
sleep_prov,
auth_material,
my_addrs,
peer_target,
memquota,
create_request_handler,
)
}
#[expect(clippy::too_many_arguments)]
pub fn accept<T, S>(
self,
peer_addr: Sensitive<PeerAddr>,
my_addrs: Vec<SocketAddr>,
tls: T,
sleep_prov: S,
auth_material: Arc<RelayChannelAuthMaterial>,
memquota: ChannelAccount,
create_request_handler: Arc<CreateRequestHandler>,
) -> RelayResponderHandshake<T, S>
where
T: AsyncRead + AsyncWrite + CertifiedConn + StreamOps + Send + Unpin + 'static,
S: CoarseTimeProvider + SleepProvider,
{
RelayResponderHandshake::new(
peer_addr,
my_addrs,
tls,
sleep_prov,
auth_material,
memquota,
create_request_handler,
)
}
}
pub(crate) struct ChannelAuthenticationData {
pub(crate) link_auth: u16,
pub(crate) cid: [u8; 32],
pub(crate) sid: [u8; 32],
pub(crate) cid_ed: [u8; 32],
pub(crate) sid_ed: [u8; 32],
pub(crate) clog: ClogDigest,
pub(crate) slog: SlogDigest,
pub(crate) scert: [u8; 32],
}
impl ChannelAuthenticationData {
const fn auth_type_bytes(link_auth: u16) -> Result<&'static [u8]> {
match link_auth {
3 => Ok(b"AUTH0003"),
_ => Err(Error::BadCellAuth),
}
}
const fn keying_material_label_bytes(link_auth: u16) -> Result<&'static [u8]> {
match link_auth {
3 => Ok(b"EXPORTER FOR TOR TLS CLIENT BINDING AUTH0003"),
_ => Err(Error::BadCellAuth),
}
}
pub(crate) fn as_body_no_rand<C: CertifiedConn>(&self, tls: &C) -> Result<Vec<u8>> {
let mut body = Vec::with_capacity(msg::Authenticate::BODY_LEN);
body.extend_from_slice(Self::auth_type_bytes(self.link_auth)?);
body.extend_from_slice(&self.cid);
body.extend_from_slice(&self.sid);
body.extend_from_slice(&self.cid_ed);
body.extend_from_slice(&self.sid_ed);
body.extend_from_slice(self.slog.as_ref());
body.extend_from_slice(self.clog.as_ref());
body.extend_from_slice(&self.scert);
let tls_secrets = tls.export_keying_material(
32,
Self::keying_material_label_bytes(self.link_auth)?,
Some(&self.cid[..]),
)?;
body.extend_from_slice(tls_secrets.as_slice());
debug_assert_eq!(body.len(), msg::Authenticate::BODY_LEN);
debug_assert_eq!(body.capacity(), msg::Authenticate::BODY_LEN);
Ok(body)
}
pub(crate) fn into_authenticate<C: CertifiedConn>(
self,
tls: &C,
link_ed: &RelayLinkSigningKeypair,
) -> Result<msg::Authenticate> {
let mut body = self.as_body_no_rand(tls)?;
let random: [u8; 24] = rand::rng().random();
body.extend_from_slice(&random);
let sig = link_ed.sign(&body);
body.extend_from_slice(&sig.to_bytes());
Ok(msg::Authenticate::new(self.link_auth, body))
}
pub(crate) fn build_initiator<T, S>(
auth_challenge_cell: &msg::AuthChallenge,
auth_material: &Arc<RelayChannelAuthMaterial>,
clog: ClogDigest,
slog: SlogDigest,
verified: &mut VerifiedChannel<T, S>,
peer_cert_digest: [u8; 32],
) -> Result<ChannelAuthenticationData>
where
T: AsyncRead + AsyncWrite + CertifiedConn + StreamOps + Send + Unpin + 'static,
S: CoarseTimeProvider + SleepProvider,
{
let link_auth = *LINK_AUTH
.iter()
.filter(|m| auth_challenge_cell.methods().contains(m))
.max()
.ok_or(Error::BadCellAuth)?;
let cid = auth_material.rsa_id_der_digest;
let sid = verified.peer_rsa_id_digest;
let cid_ed = auth_material.ed_id_bytes();
let sid_ed = (*verified
.relay_ids()
.ed_identity()
.expect("Verified channel without Ed25519 identity"))
.into();
Ok(Self {
link_auth,
cid,
sid,
cid_ed,
sid_ed,
clog,
slog,
scert: peer_cert_digest,
})
}
pub(crate) fn build_responder(
initiator_auth_type: u16,
auth_material: &Arc<RelayChannelAuthMaterial>,
clog: ClogDigest,
slog: SlogDigest,
peer_rsa_id_digest: [u8; 32],
peer_relayid_ed: Ed25519Identity,
our_cert_digest: [u8; 32],
) -> Result<ChannelAuthenticationData> {
let link_auth = if LINK_AUTH.contains(&initiator_auth_type) {
initiator_auth_type
} else {
return Err(Error::UnsupportedAuth(initiator_auth_type));
};
let cid = auth_material.rsa_id_der_digest;
let sid = peer_rsa_id_digest;
let cid_ed = auth_material.ed_id_bytes();
let sid_ed = peer_relayid_ed.into();
Ok(Self {
link_auth,
cid: sid,
sid: cid,
cid_ed: sid_ed,
sid_ed: cid_ed,
clog,
slog,
scert: our_cert_digest,
})
}
}
pub(crate) fn build_certs_cell(
auth_material: &Arc<RelayChannelAuthMaterial>,
is_responder: bool,
) -> msg::Certs {
let mut certs = msg::Certs::new_empty();
certs.push_cert_body(
tor_cert::CertType::RSA_ID_X509,
auth_material.cert_id_x509_rsa.clone(),
);
certs.push_cert(&auth_material.cert_id_rsa);
certs.push_cert(&auth_material.cert_id_sign_ed);
if is_responder {
certs.push_cert(&auth_material.cert_sign_tls_ed);
} else {
certs.push_cert(&auth_material.cert_sign_link_auth_ed);
}
certs
}
pub(crate) fn build_netinfo_cell<S>(
peer_ip: Option<IpAddr>,
my_addrs: Vec<IpAddr>,
sleep_prov: &S,
) -> Result<msg::Netinfo>
where
S: CoarseTimeProvider + SleepProvider,
{
let timestamp = sleep_prov
.wallclock()
.duration_since(UNIX_EPOCH)
.map_err(|e| internal!("Wallclock may have gone backwards: {e}"))?
.as_secs()
.try_into()
.map_err(|e| internal!("Wallclock secs fail to convert to 32bit: {e}"))?;
Ok(msg::Netinfo::from_relay(timestamp, peer_ip, my_addrs))
}
#[cfg(test)]
pub(crate) mod test {
#![allow(clippy::unwrap_used)]
use futures::channel::mpsc::{Receiver, Sender};
use futures::task::{Context, Poll};
use futures::{AsyncRead, AsyncWrite};
use std::borrow::Cow;
use std::io::Result as IoResult;
use std::pin::Pin;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::{Duration, SystemTime};
use tor_basic_utils::test_rng::testing_rng;
use tor_cell::chancell::AnyChanCell;
use tor_cert::x509::TlsKeyAndCert;
use tor_key_forge::{Keygen, ToEncodableCert};
use tor_linkspec::OwnedChanTarget;
use tor_relay_crypto::pk::{
RelayIdentityKeypair, RelayIdentityRsaKeypair, RelayLinkSigningKeypair, RelaySigningKeypair,
};
use tor_relay_crypto::{gen_link_cert, gen_signing_cert, gen_tls_cert};
use tor_rtcompat::{CertifiedConn, Runtime, SpawnExt, StreamOps};
use web_time_compat::SystemTimeExt;
use crate::channel::Channel;
use crate::channel::handler::test::MsgBuf;
use crate::channel::test::{CodecResult, new_reactor};
use crate::circuit::UniqId;
use crate::relay::channel::RelayChannelAuthMaterial;
use crate::relay::channel_provider::{ChannelProvider, OutboundChanSender};
pub(crate) struct RelayMsgBuf(pub(crate) MsgBuf);
impl AsyncRead for RelayMsgBuf {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<IoResult<usize>> {
Pin::new(&mut self.0).poll_read(cx, buf)
}
}
impl AsyncWrite for RelayMsgBuf {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<IoResult<usize>> {
Pin::new(&mut self.0).poll_write(cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
Pin::new(&mut self.0).poll_flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
Pin::new(&mut self.0).poll_close(cx)
}
}
impl StreamOps for RelayMsgBuf {}
impl CertifiedConn for RelayMsgBuf {
fn export_keying_material(
&self,
len: usize,
_label: &[u8],
_context: Option<&[u8]>,
) -> IoResult<Vec<u8>> {
Ok(vec![42_u8; len])
}
fn peer_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
const ISSUER: &str = "issuer.peer_tls.test.nowar.net";
const SUBJECT: &str = "subject.peer_tls.test.nowar.net";
Ok(Some(fake_tls_cert(ISSUER, SUBJECT)))
}
fn own_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
const ISSUER: &str = "issuer.own_tls.test.nowar.net";
const SUBJECT: &str = "subject.own_tls.test.nowar.net";
Ok(Some(fake_tls_cert(ISSUER, SUBJECT)))
}
}
fn fake_tls_cert<'a>(issuer: &'a str, subject: &'a str) -> Cow<'a, [u8]> {
let mut rng = testing_rng();
let tls_cert = TlsKeyAndCert::create(&mut rng, SystemTime::get(), issuer, subject).unwrap();
Cow::Owned(tls_cert.certificates_der()[0].to_vec())
}
pub(crate) struct DummyChanProvider<R> {
runtime: R,
outbound: Arc<Mutex<Option<DummyChan>>>,
}
impl<R: Runtime> DummyChanProvider<R> {
pub(crate) fn new(runtime: R, outbound: Arc<Mutex<Option<DummyChan>>>) -> Self {
Self { runtime, outbound }
}
pub(crate) fn new_without_chan(runtime: R) -> Self {
Self {
runtime,
outbound: Arc::new(Mutex::new(None)),
}
}
}
impl<R: Runtime> ChannelProvider for DummyChanProvider<R> {
type BuildSpec = OwnedChanTarget;
fn get_or_launch(
self: Arc<Self>,
_reactor_id: UniqId,
_target: Self::BuildSpec,
tx: OutboundChanSender,
) -> crate::Result<()> {
let dummy_chan = working_dummy_channel(&self.runtime);
let chan = Arc::clone(&dummy_chan.channel);
{
let mut lock = self.outbound.lock().unwrap();
assert!(lock.is_none());
*lock = Some(dummy_chan);
}
tx.send(Ok(chan));
Ok(())
}
}
pub(crate) struct DummyChan {
pub(crate) rx: Receiver<AnyChanCell>,
pub(crate) tx: Sender<CodecResult>,
pub(crate) channel: Arc<Channel>,
}
pub(crate) fn working_dummy_channel<R: Runtime>(rt: &R) -> DummyChan {
let (channel, chan_reactor, rx, tx) = new_reactor(rt.clone());
rt.spawn(async {
let _ignore = chan_reactor.run().await;
})
.unwrap();
DummyChan { tx, rx, channel }
}
pub(crate) fn fake_auth_material() -> Arc<RelayChannelAuthMaterial> {
const KEY_DURATION_2DAYS: Duration = Duration::from_secs(2 * 24 * 60 * 60);
const KEY_DURATION_30DAYS: Duration = Duration::from_secs(30 * 24 * 60 * 60);
static AUTH: OnceLock<Arc<RelayChannelAuthMaterial>> = OnceLock::new();
AUTH.get_or_init(|| {
let now = SystemTime::get();
let mut rng = tor_llcrypto::rng::CautiousRng;
let issuer_hostname = "issuer.test.nowar.net";
let subject_hostname = "subject.test.nowar.net";
let kp_relayid_rsa = RelayIdentityRsaKeypair::generate(&mut rng).unwrap();
let kp_relayid_ed = RelayIdentityKeypair::generate(&mut rng).unwrap();
let kp_relaysign_ed = RelaySigningKeypair::generate(&mut rng).unwrap();
let kp_link_ed = RelayLinkSigningKeypair::generate(&mut rng).unwrap();
let tls_key_and_cert =
TlsKeyAndCert::create(&mut rng, now, issuer_hostname, subject_hostname).unwrap();
let cert_id_sign_ed =
gen_signing_cert(&kp_relayid_ed, &kp_relaysign_ed, now + KEY_DURATION_30DAYS)
.unwrap();
let cert_sign_link_auth_ed =
gen_link_cert(&kp_relaysign_ed, &kp_link_ed, now + KEY_DURATION_2DAYS).unwrap();
let cert_sign_tls_ed = gen_tls_cert(
&kp_relaysign_ed,
*tls_key_and_cert.link_cert_sha256(),
now + KEY_DURATION_2DAYS,
)
.unwrap();
let cert_id_rsa = tor_cert::rsa::EncodedRsaCrosscert::encode_and_sign(
kp_relayid_rsa.keypair(),
&kp_relayid_ed.to_ed25519_id(),
now + KEY_DURATION_2DAYS,
)
.unwrap();
let cert_id_x509_rsa = tor_cert::x509::create_legacy_rsa_id_cert(
&mut rng,
now,
issuer_hostname,
kp_relayid_rsa.keypair(),
)
.unwrap();
Arc::new(RelayChannelAuthMaterial::new(
&kp_relayid_rsa.public().into(),
kp_relayid_ed.to_ed25519_id(),
kp_link_ed,
cert_id_sign_ed.to_encodable_cert(),
cert_sign_tls_ed,
cert_sign_link_auth_ed.to_encodable_cert(),
cert_id_x509_rsa,
cert_id_rsa,
tls_key_and_cert,
))
})
.clone()
}
}