1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
//! Hidden service (onion service) client key management functionality
// TODO HS what layer should be responsible for finding and dispatching keys?
// I think it should be as high as possible, so keys should be passed into
// the hs connector for each connection. Otherwise there would have to be an
// HsKeyProvider trait here, and error handling gets complicated.
use std::fmt::{self, Debug};
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use tor_hscrypto::pk::{HsClientDescEncSecretKey, HsClientIntroAuthKeypair, HsId};
use tor_keymgr::{ArtiPath, ArtiPathComponent, CTorPath, KeySpecifier};
/// Keys (if any) to use when connecting to a specific onion service.
///
/// Represents a possibly empty subset of the following keys:
/// * `KS_hsc_desc_enc`, [`HsClientDescEncSecretKey`]
/// * `KS_hsc_intro_auth`, [`HsClientIntroAuthKeypair`]
///
/// `HsClientSecretKeys` is constructed with a `Builder`:
/// use `ClientSecretKeysBuilder::default()`,
/// optionally call setters, and then call `build()`.
///
/// For client connections to share circuits and streams,
/// call `build` only once.
/// Different calls to `build` yield `HsClientSecretKeys` values
/// which won't share HS circuits, streams, or authentication.
///
/// Conversely, `Clone`s of an `HsClientSecretKeys` *can* share circuits.
//
/// All [empty](HsClientSecretKeys::is_empty) `HsClientSecretKeys`
/// (for example, from [`:none()`](HsClientSecretKeys::none))
/// *can* share circuits.
//
// TODO HS some way to read these from files or something!
//
// TODO HS: some of our APIs take Option<HsClientSecretKeys>.
// But HsClientSecretKeys is can be empty, so we should remove the `Option`.
#[derive(Clone, Default)]
pub struct HsClientSecretKeys {
/// The actual keys
///
/// This is compared and hashed by the Arc pointer value.
/// We don't want to implement key comparison by comparing secret key values.
pub(crate) keys: Arc<ClientSecretKeyValues>,
}
impl Debug for HsClientSecretKeys {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO derive this?
let mut d = f.debug_tuple("HsClientSecretKeys");
d.field(&Arc::as_ptr(&self.keys));
self.keys
.ks_hsc_desc_enc
.as_ref()
.map(|_| d.field(&"<desc_enc>"));
self.keys
.ks_hsc_intro_auth
.as_ref()
.map(|_| d.field(&"<intro_uath>"));
d.finish()
}
}
impl PartialEq for HsClientSecretKeys {
fn eq(&self, other: &Self) -> bool {
self.is_empty() && other.is_empty() || Arc::ptr_eq(&self.keys, &other.keys)
}
}
impl Eq for HsClientSecretKeys {}
impl Hash for HsClientSecretKeys {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.keys).hash(state);
}
}
impl HsClientSecretKeys {
/// Create a new `HsClientSecretKeys`, for making unauthenticated connections
///
/// Creates a `HsClientSecretKeys` which has no actual keys,
/// so will make connections to hidden services
/// without any Tor-protocol-level client authentication.
pub fn none() -> Self {
Self::default()
}
/// Tests whether this `HsClientSecretKeys` actually contains any keys
pub fn is_empty(&self) -> bool {
// TODO derive this. For now, we deconstruct it to prove we check all the fields.
let ClientSecretKeyValues {
ks_hsc_desc_enc,
ks_hsc_intro_auth,
} = &*self.keys;
ks_hsc_desc_enc.is_none() && ks_hsc_intro_auth.is_none()
}
}
/// Client secret key values
///
/// Skip the whole builder pattern derivation, etc. - the types are just the same
type ClientSecretKeyValues = HsClientSecretKeysBuilder;
/// Builder for `HsClientSecretKeys`
#[derive(Default, Debug)]
pub struct HsClientSecretKeysBuilder {
/// Possibly, a key that is used to decrypt a descriptor.
pub(crate) ks_hsc_desc_enc: Option<HsClientDescEncSecretKey>,
/// Possibly, a key that is used to authenticate while introducing.
pub(crate) ks_hsc_intro_auth: Option<HsClientIntroAuthKeypair>,
}
// TODO derive these setters
//
// TODO HS is this what we want for an API? We need *some* API.
// This is a bit like config but we probably don't want to
// feed secret key material through config-rs, etc.
impl HsClientSecretKeysBuilder {
/// Provide a descriptor decryption key
pub fn ks_hsc_desc_enc(&mut self, ks: HsClientDescEncSecretKey) -> &mut Self {
self.ks_hsc_desc_enc = Some(ks);
self
}
/// Provide an introduction authentication key
pub fn ks_hsc_intro_auth(&mut self, ks: HsClientIntroAuthKeypair) -> &mut Self {
self.ks_hsc_intro_auth = Some(ks);
self
}
/// Convert these
pub fn build(self) -> Result<HsClientSecretKeys, tor_config::ConfigBuildError> {
Ok(HsClientSecretKeys {
keys: Arc::new(self),
})
}
}
/// An HS client identifier.
///
/// Distinguishes different "clients" or "users" of this Arti instance,
/// so that they can have different sets of HS client authentication keys.
#[derive(Clone, Debug, derive_more::Display, derive_more::Into, derive_more::AsRef)]
pub struct HsClientSpecifier(ArtiPathComponent);
impl HsClientSpecifier {
/// Create a new [`HsClientSpecifier`].
///
/// The `inner` string **must** be a valid [`ArtiPathComponent`].
pub fn new(inner: String) -> Result<Self, tor_keymgr::Error> {
ArtiPathComponent::new(inner).map(Self)
}
}
/// An identifier for a particular instance of an HS client key.
pub struct HsClientSecretKeySpecifier {
/// The client associated with this key.
client_id: HsClientSpecifier,
/// The hidden service this authorization key is for.
hs_id: HsId,
/// The role of the key.
role: HsClientKeyRole,
}
/// The role of an HS client key.
#[derive(Debug, Clone, Copy, PartialEq, derive_more::Display)]
#[non_exhaustive]
pub enum HsClientKeyRole {
/// A key for deriving keys for decrypting HS descriptors (KP_hsc_desc_enc).
#[display(fmt = "KP_hsc_desc_enc")]
DescEnc,
/// A key for computing INTRODUCE1 signatures (KP_hsc_intro_auth).
#[display(fmt = "KP_hsc_intro_auth")]
IntroAuth,
}
impl HsClientSecretKeySpecifier {
/// Create a new [`HsClientSecretKeySpecifier`].
pub fn new(client_id: HsClientSpecifier, hs_id: HsId, role: HsClientKeyRole) -> Self {
Self {
client_id,
hs_id,
role,
}
}
}
impl KeySpecifier for HsClientSecretKeySpecifier {
fn arti_path(&self) -> tor_keymgr::Result<ArtiPath> {
ArtiPath::new(format!(
"client/{}/{}/{}",
self.client_id, self.hs_id, self.role
))
}
fn ctor_path(&self) -> Option<CTorPath> {
todo!()
}
}