Add a set of Identity-related types and accessors.

I wonder if these types are correct.  I think it makes sense to have
a Ref type like this, rather than just using `&RelayId`, but it
doesn't seems that I can make `RelayId` and `RelayIdRef` implement
Borrow and ToOwned for one another, so maybe I've messed up.
This commit is contained in:
Nick Mathewson 2022-08-03 11:26:53 -04:00
parent 62850a24c9
commit 9d4729a072
6 changed files with 168 additions and 2 deletions

2
Cargo.lock generated
View File

@ -3717,8 +3717,10 @@ dependencies = [
name = "tor-linkspec" name = "tor-linkspec"
version = "0.4.0" version = "0.4.0"
dependencies = [ dependencies = [
"derive_more",
"hex-literal", "hex-literal",
"serde", "serde",
"strum",
"tor-bytes", "tor-bytes",
"tor-llcrypto", "tor-llcrypto",
"tor-protover", "tor-protover",

View File

@ -12,7 +12,9 @@ categories = ["network-programming"]
repository = "https://gitlab.torproject.org/tpo/core/arti.git/" repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[dependencies] [dependencies]
derive_more = "0.99"
serde = { version = "1.0.103", features = ["derive"] } serde = { version = "1.0.103", features = ["derive"] }
strum = { version = "0.24", features = ["derive"] }
tor-bytes = { path = "../tor-bytes", version = "0.5.0" } tor-bytes = { path = "../tor-bytes", version = "0.5.0" }
tor-llcrypto = { path = "../tor-llcrypto", version = "0.3.3" } tor-llcrypto = { path = "../tor-llcrypto", version = "0.3.3" }
tor-protover = { path = "../tor-protover", version = "0.3.0" } tor-protover = { path = "../tor-protover", version = "0.3.0" }

View File

@ -0,0 +1,105 @@
//! Code to abstract over the notion of relays having one or more identities.
//!
//! Currently (2022), every Tor relay has exactly two identities: A legacy
//! identity that is based on the SHA-1 hash of an RSA-1024 public key, and a
//! modern identity that is an Ed25519 public key. This code lets us abstract
//! over those types, and over other new types that may exist in the future.
use derive_more::{Display, From};
use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
/// The type of a relay identity.
///
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Display, strum::EnumIter)]
#[non_exhaustive]
pub enum RelayIdType {
/// An Ed25519 identity.
///
/// Every relay (currently) has one of these identities. It is the same
/// as the encoding of the relay's public Ed25519 identity key.
#[display(fmt = "Ed25519")]
Ed25519,
/// An RSA identity.
///
/// Every relay (currently) has one of these identities. It is computed as
/// a SHA-1 digest of the DER encoding of the relay's public RSA 1024-bit
/// identity key. Because of short key length, this type of identity should
/// not be considered secure on its own.
#[display(fmt = "RSA (legacy)")]
Rsa,
}
/// A single relay identity.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Display, From, Hash)]
#[non_exhaustive]
pub enum RelayId {
/// An Ed25519 identity.
#[display(fmt = "{}", _0)]
Ed25519(Ed25519Identity),
/// An RSA identity.
#[display(fmt = "{}", _0)]
Rsa(RsaIdentity),
}
/// A reference to a single relay identity.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, From, derive_more::TryInto)]
#[non_exhaustive]
pub enum RelayIdRef<'a> {
/// An Ed25519 identity.
#[display(fmt = "{}", _0)]
Ed25519(&'a Ed25519Identity),
/// An RSA identity.
#[display(fmt = "{}", _0)]
Rsa(&'a RsaIdentity),
}
impl RelayIdType {
/// Return an iterator over all
pub fn all_types() -> RelayIdTypeIter {
use strum::IntoEnumIterator;
Self::iter()
}
}
impl RelayId {
/// Return a [`RelayIdRef`] pointing to the contents of this identity.
pub fn as_ref(&self) -> RelayIdRef<'_> {
match self {
RelayId::Ed25519(key) => key.into(),
RelayId::Rsa(key) => key.into(),
}
}
}
impl<'a> RelayIdRef<'a> {
/// Copy this reference into a new [`RelayId`] object.
//
// TODO(nickm): I wish I could make this a proper `ToOwned` implementation,
// but I see no way to do as long as RelayIdRef<'a> implements Clone too.
pub fn to_owned(&self) -> RelayId {
match *self {
RelayIdRef::Ed25519(key) => (*key).into(),
RelayIdRef::Rsa(key) => (*key).into(),
}
}
}
/// Expand to an implementation for PartialEq for a given key type.
macro_rules! impl_eq_variant {
{ $var:ident($type:ty) } => {
impl<'a> PartialEq<$type> for RelayIdRef<'a> {
fn eq(&self, other: &$type) -> bool {
matches!(self, RelayIdRef::$var(this) if this == &other)
}
}
impl PartialEq<$type> for RelayId {
fn eq(&self, other: &$type) -> bool {
matches!(&self, RelayId::$var(this) if this == other)
}
}
}
}
impl_eq_variant! { Rsa(RsaIdentity) }
impl_eq_variant! { Ed25519(Ed25519Identity) }

View File

@ -72,10 +72,12 @@
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945 #![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
//! <!-- @@ end lint list maintained by maint/add_warning @@ --> //! <!-- @@ end lint list maintained by maint/add_warning @@ -->
mod ids;
mod ls; mod ls;
mod owned; mod owned;
mod traits; mod traits;
pub use ids::{RelayId, RelayIdRef, RelayIdType, RelayIdTypeIter};
pub use ls::LinkSpec; pub use ls::LinkSpec;
pub use owned::{OwnedChanTarget, OwnedCircTarget, RelayIds}; pub use owned::{OwnedChanTarget, OwnedCircTarget, RelayIds};
pub use traits::{ChanTarget, CircTarget, HasAddrs, HasRelayIds}; pub use traits::{ChanTarget, CircTarget, HasAddrs, HasRelayIds};

View File

@ -9,6 +9,8 @@ use tor_bytes::{EncodeResult, Error, Readable, Reader, Result, Writeable, Writer
use tor_llcrypto::pk::ed25519; use tor_llcrypto::pk::ed25519;
use tor_llcrypto::pk::rsa::RsaIdentity; use tor_llcrypto::pk::rsa::RsaIdentity;
use crate::RelayId;
/// A piece of information about a relay and how to connect to it. /// A piece of information about a relay and how to connect to it.
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -131,6 +133,14 @@ impl From<ed25519::PublicKey> for LinkSpec {
LinkSpec::Ed25519Id(pk.into()) LinkSpec::Ed25519Id(pk.into())
} }
} }
impl From<RelayId> for LinkSpec {
fn from(id: RelayId) -> Self {
match id {
RelayId::Ed25519(key) => LinkSpec::Ed25519Id(key),
RelayId::Rsa(key) => LinkSpec::RsaId(key),
}
}
}
impl LinkSpec { impl LinkSpec {
/// Helper: return the position in the list of identifiers /// Helper: return the position in the list of identifiers

View File

@ -1,11 +1,32 @@
//! Declare traits to be implemented by types that describe a place //! Declare traits to be implemented by types that describe a place
//! that Tor can connect to, directly or indirectly. //! that Tor can connect to, directly or indirectly.
use std::net::SocketAddr; use std::{iter::FusedIterator, net::SocketAddr};
use tor_llcrypto::pk; use tor_llcrypto::pk;
use crate::{RelayIdRef, RelayIdType, RelayIdTypeIter};
/// An object containing information about a relay's identity keys. /// An object containing information about a relay's identity keys.
pub trait HasRelayIds { pub trait HasRelayIds {
/// Return the identity of this relay whose type is `key_type`, or None if
/// the relay has no such identity.
///
/// (Currently all relays have all recognized identity types, but we might
/// implement or deprecate an identity type in the future.)
fn identity(&self, key_type: RelayIdType) -> Option<RelayIdRef<'_>> {
match key_type {
RelayIdType::Rsa => Some(self.rsa_identity().into()),
RelayIdType::Ed25519 => Some(self.ed_identity().into()),
}
}
/// Return an iterator over all of the identities held by this object.
fn identities(&self) -> RelayIdIter<'_, Self> {
RelayIdIter {
info: self,
next_key: RelayIdType::all_types(),
}
}
/// Return the ed25519 identity for this relay. /// Return the ed25519 identity for this relay.
fn ed_identity(&self) -> &pk::ed25519::Ed25519Identity; fn ed_identity(&self) -> &pk::ed25519::Ed25519Identity;
/// Return the RSA identity for this relay. /// Return the RSA identity for this relay.
@ -28,6 +49,30 @@ pub trait HasRelayIds {
} }
} }
/// An iterator over all of the relay identities held by a [`HasRelayIds`]
#[derive(Clone)]
pub struct RelayIdIter<'a, T: HasRelayIds + ?Sized> {
/// The object holding the keys
info: &'a T,
/// The next key type to yield
next_key: RelayIdTypeIter,
}
impl<'a, T: HasRelayIds + ?Sized> Iterator for RelayIdIter<'a, T> {
type Item = RelayIdRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
for key_type in &mut self.next_key {
if let Some(key) = self.info.identity(key_type) {
return Some(key);
}
}
None
}
}
// RelayIdIter is fused since next_key is fused.
impl<'a, T: HasRelayIds + ?Sized> FusedIterator for RelayIdIter<'a, T> {}
/// An object that represents a host on the network with known IP addresses. /// An object that represents a host on the network with known IP addresses.
pub trait HasAddrs { pub trait HasAddrs {
/// Return the addresses at which you can connect to this server. /// Return the addresses at which you can connect to this server.
@ -54,7 +99,7 @@ pub trait CircTarget: ChanTarget {
// of link specifiers, but that's not so easy to do, since it seems // of link specifiers, but that's not so easy to do, since it seems
// doing so correctly would require default associated types. // doing so correctly would require default associated types.
fn linkspecs(&self) -> Vec<crate::LinkSpec> { fn linkspecs(&self) -> Vec<crate::LinkSpec> {
let mut result = vec![(*self.ed_identity()).into(), (*self.rsa_identity()).into()]; let mut result: Vec<_> = self.identities().map(|id| id.to_owned().into()).collect();
for addr in self.addrs().iter() { for addr in self.addrs().iter() {
result.push(addr.into()); result.push(addr.into());
} }