diff --git a/content-discovery/Cargo.toml b/content-discovery/Cargo.toml index fe2e191..0d01186 100644 --- a/content-discovery/Cargo.toml +++ b/content-discovery/Cargo.toml @@ -3,6 +3,7 @@ members = [ "iroh-mainline-content-discovery", "iroh-mainline-content-discovery-cli", "iroh-mainline-tracker", + "tls", ] resolver = "2" @@ -26,5 +27,6 @@ missing_debug_implementations = "warn" unused-async = "warn" [workspace.dependencies] -iroh = "0.29" -iroh-blobs = "0.29" +iroh = "0.30" +iroh-base = "0.30" +iroh-blobs = { version = "0.30", features = ["rpc"] } diff --git a/content-discovery/iroh-mainline-content-discovery-cli/src/args.rs b/content-discovery/iroh-mainline-content-discovery-cli/src/args.rs index fe15190..294e2a9 100644 --- a/content-discovery/iroh-mainline-content-discovery-cli/src/args.rs +++ b/content-discovery/iroh-mainline-content-discovery-cli/src/args.rs @@ -1,6 +1,7 @@ //! Command line arguments. use clap::{Parser, Subcommand}; -use iroh::{ticket::BlobTicket, NodeId}; +use iroh::NodeId; +use iroh_blobs::ticket::BlobTicket; use iroh_blobs::{Hash, HashAndFormat}; use std::{ fmt::Display, diff --git a/content-discovery/iroh-mainline-content-discovery-cli/src/main.rs b/content-discovery/iroh-mainline-content-discovery-cli/src/main.rs index 40aa37e..b47dd16 100644 --- a/content-discovery/iroh-mainline-content-discovery-cli/src/main.rs +++ b/content-discovery/iroh-mainline-content-discovery-cli/src/main.rs @@ -24,7 +24,7 @@ async fn announce(args: AnnounceArgs) -> anyhow::Result<()> { eprintln!("ANNOUNCE_SECRET environment variable must be set to a valid secret key"); anyhow::bail!("ANNOUNCE_SECRET env var not set"); }; - let Ok(key) = iroh::key::SecretKey::from_str(&key) else { + let Ok(key) = iroh::SecretKey::from_str(&key) else { anyhow::bail!("ANNOUNCE_SECRET env var is not a valid secret key"); }; let content = args.content.hash_and_format(); diff --git a/content-discovery/iroh-mainline-content-discovery/Cargo.toml b/content-discovery/iroh-mainline-content-discovery/Cargo.toml index 9d34d5b..45559f8 100644 --- a/content-discovery/iroh-mainline-content-discovery/Cargo.toml +++ b/content-discovery/iroh-mainline-content-discovery/Cargo.toml @@ -12,7 +12,9 @@ license = "MIT OR Apache-2.0" # # The protocol is using postcard, but we don't need a postcard dependency for just the type definitions iroh = { workspace = true } +iroh-base = { workspace = true } iroh-blobs = { workspace = true } +rand = "0.8.5" serde = { version = "1", features = ["derive"] } derive_more = { version = "1.0.0-beta.1", features = ["debug", "display", "from", "try_into"] } serde-big-array = "0.5.1" @@ -30,7 +32,8 @@ rustls = { version = "0.23", default-features = false, features = ["ring"], opti genawaiter = { version = "0.99.1", features = ["futures03"], optional = true } tokio = { version = "1.36.0", optional = true } flume = "0.11.0" +tls = { path = "../tls", optional = true } [features] -client = ["mainline", "iroh-quinn", "tracing", "anyhow", "rcgen", "genawaiter", "rustls", "futures", "postcard", "tokio"] +client = ["mainline", "iroh-quinn", "tracing", "anyhow", "rcgen", "genawaiter", "rustls", "futures", "postcard", "tokio", "tls"] default = ["client"] diff --git a/content-discovery/iroh-mainline-content-discovery/src/client.rs b/content-discovery/iroh-mainline-content-discovery/src/client.rs index fd82d64..89ec3ae 100644 --- a/content-discovery/iroh-mainline-content-discovery/src/client.rs +++ b/content-discovery/iroh-mainline-content-discovery/src/client.rs @@ -210,9 +210,8 @@ pub fn create_quinn_client( alpn_protocols: Vec<Vec<u8>>, keylog: bool, ) -> anyhow::Result<iroh_quinn::Endpoint> { - let secret_key = iroh::key::SecretKey::generate(); - let tls_client_config = - iroh::tls::make_client_config(&secret_key, None, alpn_protocols, keylog)?; + let secret_key = iroh::SecretKey::generate(rand::thread_rng()); + let tls_client_config = tls::make_client_config(&secret_key, None, alpn_protocols, keylog)?; let mut client_config = iroh_quinn::ClientConfig::new(Arc::new(tls_client_config)); let mut endpoint = iroh_quinn::Endpoint::client(bind_addr)?; let mut transport_config = iroh_quinn::TransportConfig::default(); @@ -223,7 +222,7 @@ pub fn create_quinn_client( } async fn create_endpoint( - key: iroh::key::SecretKey, + key: iroh::SecretKey, ipv4_addr: SocketAddrV4, ipv6_addr: SocketAddrV6, publish: bool, @@ -301,7 +300,7 @@ async fn connect_iroh( // todo: uncomment once the connection problems are fixed // for now, a random node id is more reliable. // let key = load_secret_key(tracker_path(CLIENT_KEY)?).await?; - let key = iroh::key::SecretKey::generate(); + let key = iroh::SecretKey::generate(rand::thread_rng()); let endpoint = create_endpoint(key, local_ipv4_addr, local_ipv6_addr, false).await?; tracing::info!("trying to connect to tracker at {:?}", tracker); let connection = endpoint.connect(tracker, ALPN).await?; diff --git a/content-discovery/iroh-mainline-content-discovery/src/protocol.rs b/content-discovery/iroh-mainline-content-discovery/src/protocol.rs index a6fa2d9..5e7ffb1 100644 --- a/content-discovery/iroh-mainline-content-discovery/src/protocol.rs +++ b/content-discovery/iroh-mainline-content-discovery/src/protocol.rs @@ -118,7 +118,7 @@ impl Deref for SignedAnnounce { impl SignedAnnounce { /// Create a new signed announce. - pub fn new(announce: Announce, secret_key: &iroh::key::SecretKey) -> anyhow::Result<Self> { + pub fn new(announce: Announce, secret_key: &iroh::SecretKey) -> anyhow::Result<Self> { let announce_bytes = postcard::to_allocvec(&announce)?; let signature = secret_key.sign(&announce_bytes).to_bytes(); Ok(Self { @@ -130,7 +130,7 @@ impl SignedAnnounce { /// Verify the announce, and return the announce if it's valid. pub fn verify(&self) -> anyhow::Result<()> { let announce_bytes = postcard::to_allocvec(&self.announce)?; - let signature = iroh::key::Signature::from_bytes(&self.signature); + let signature = iroh_base::Signature::from_bytes(&self.signature); self.announce.host.verify(&announce_bytes, &signature)?; Ok(()) } diff --git a/content-discovery/iroh-mainline-tracker/Cargo.toml b/content-discovery/iroh-mainline-tracker/Cargo.toml index 6641751..5e4f840 100644 --- a/content-discovery/iroh-mainline-tracker/Cargo.toml +++ b/content-discovery/iroh-mainline-tracker/Cargo.toml @@ -41,6 +41,7 @@ url = "2.5.0" flume = "0.11.0" genawaiter = { version = "0.99.1", features = ["futures03"] } iroh-mainline-content-discovery = { path = "../iroh-mainline-content-discovery", features = ["client"] } +tls = { path = "../tls" } clap = { version = "4", features = ["derive"], optional = true } serde-big-array = "0.5.1" diff --git a/content-discovery/iroh-mainline-tracker/src/main.rs b/content-discovery/iroh-mainline-tracker/src/main.rs index 1b32fc8..ecb70cb 100644 --- a/content-discovery/iroh-mainline-tracker/src/main.rs +++ b/content-discovery/iroh-mainline-tracker/src/main.rs @@ -62,7 +62,7 @@ async fn await_relay_region(endpoint: &Endpoint) -> anyhow::Result<()> { } async fn create_endpoint( - key: iroh::key::SecretKey, + key: iroh::SecretKey, ipv4_addr: SocketAddrV4, publish: bool, ) -> anyhow::Result<Endpoint> { @@ -187,18 +187,18 @@ async fn main() -> anyhow::Result<()> { /// Returns default server configuration along with its certificate. #[allow(clippy::field_reassign_with_default)] // https://github.com/rust-lang/rust-clippy/issues/6527 -fn configure_server(secret_key: &iroh::key::SecretKey) -> anyhow::Result<iroh_quinn::ServerConfig> { +fn configure_server(secret_key: &iroh::SecretKey) -> anyhow::Result<iroh_quinn::ServerConfig> { make_server_config(secret_key, 8, 1024, vec![ALPN.to_vec()]) } /// Create a [`quinn::ServerConfig`] with the given secret key and limits. pub fn make_server_config( - secret_key: &iroh::key::SecretKey, + secret_key: &iroh::SecretKey, max_streams: u64, max_connections: u32, alpn_protocols: Vec<Vec<u8>>, ) -> anyhow::Result<iroh_quinn::ServerConfig> { - let tls_server_config = iroh::tls::make_server_config(secret_key, alpn_protocols, false)?; + let tls_server_config = tls::make_server_config(secret_key, alpn_protocols, false)?; let mut server_config = iroh_quinn::ServerConfig::with_crypto(Arc::new(tls_server_config)); let mut transport_config = iroh_quinn::TransportConfig::default(); transport_config diff --git a/content-discovery/tls/Cargo.toml b/content-discovery/tls/Cargo.toml new file mode 100644 index 0000000..08a958c --- /dev/null +++ b/content-discovery/tls/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tls" +version = "0.1.0" +edition = "2021" +description = "create tls configuration for quic connections" +license = "MIT OR Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iroh-base = { workspace = true } +der = { version = "0.7", features = ["alloc", "derive"] } +derive_more = { version = "1.0.0-beta.1", features = ["debug", "display", "from", "try_into"] } +quinn = { package = "iroh-quinn", version = "0.12.0" } +rand = "0.8.5" +rcgen = "0.13" +ring = "0.17" +rustls = { version = "0.23", default-features = false, features = ["ring"] } +thiserror = "2" +tracing = "0.1" +webpki = { package = "rustls-webpki", version = "0.102" } +x509-parser = "0.16" diff --git a/content-discovery/tls/src/certificate.rs b/content-discovery/tls/src/certificate.rs new file mode 100644 index 0000000..9797d30 --- /dev/null +++ b/content-discovery/tls/src/certificate.rs @@ -0,0 +1,444 @@ +//! X.509 certificate handling. +//! +//! This module handles generation, signing, and verification of certificates. +//! +//! Based on rust-libp2p/transports/tls/src/certificate.rs originally licensed under MIT by Parity +//! Technologies (UK) Ltd. + +use std::sync::Arc; + +use der::{asn1::OctetStringRef, Decode, Encode, Sequence}; +use iroh_base::{PublicKey, SecretKey, Signature}; +use x509_parser::prelude::*; + +/// The libp2p Public Key Extension is a X.509 extension +/// with the Object Identifier 1.3.6.1.4.1.53594.1.1, +/// allocated by IANA to the libp2p project at Protocol Labs. +const P2P_EXT_OID: [u64; 9] = [1, 3, 6, 1, 4, 1, 53594, 1, 1]; + +/// The peer signs the concatenation of the string `libp2p-tls-handshake:` +/// and the public key that it used to generate the certificate carrying +/// the libp2p Public Key Extension, using its private host key. +/// This signature provides cryptographic proof that the peer was +/// in possession of the private host key at the time the certificate was signed. +const P2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; + +// Certificates MUST use the NamedCurve encoding for elliptic curve parameters. +// Similarly, hash functions with an output length less than 256 bits MUST NOT be used. +static P2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256; + +#[derive(Debug)] +pub(crate) struct AlwaysResolvesCert(Arc<rustls::sign::CertifiedKey>); + +impl AlwaysResolvesCert { + pub(crate) fn new( + cert: rustls::pki_types::CertificateDer<'static>, + key: &rustls::pki_types::PrivateKeyDer<'_>, + ) -> Result<Self, rustls::Error> { + let certified_key = rustls::sign::CertifiedKey::new( + vec![cert], + rustls::crypto::ring::sign::any_ecdsa_type(key)?, + ); + Ok(Self(Arc::new(certified_key))) + } +} + +impl rustls::client::ResolvesClientCert for AlwaysResolvesCert { + fn resolve( + &self, + _root_hint_subjects: &[&[u8]], + _sigschemes: &[rustls::SignatureScheme], + ) -> Option<Arc<rustls::sign::CertifiedKey>> { + Some(Arc::clone(&self.0)) + } + + fn has_certs(&self) -> bool { + true + } +} + +impl rustls::server::ResolvesServerCert for AlwaysResolvesCert { + fn resolve( + &self, + _client_hello: rustls::server::ClientHello<'_>, + ) -> Option<Arc<rustls::sign::CertifiedKey>> { + Some(Arc::clone(&self.0)) + } +} + +/// The public host key and the signature are ANS.1-encoded +/// into the SignedKey data structure, which is carried in the libp2p Public Key Extension. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +struct SignedKey<'a> { + public_key: OctetStringRef<'a>, + signature: OctetStringRef<'a>, +} + +/// Generates a self-signed TLS certificate that includes a libp2p-specific +/// certificate extension containing the public key of the given secret key. +pub fn generate( + identity_secret_key: &SecretKey, +) -> Result< + ( + rustls::pki_types::CertificateDer<'static>, + rustls::pki_types::PrivateKeyDer<'static>, + ), + GenError, +> { + // SecretKey used to sign the certificate. + // SHOULD NOT be related to the host's key. + // Endpoints MAY generate a new key and certificate + // for every connection attempt, or they MAY reuse the same key + // and certificate for multiple connections. + let certificate_keypair = rcgen::KeyPair::generate_for(P2P_SIGNATURE_ALGORITHM)?; + let rustls_key = + rustls::pki_types::PrivateKeyDer::try_from(certificate_keypair.serialize_der()) + .expect("checked"); + let certificate = { + let mut params = rcgen::CertificateParams::default(); + params.distinguished_name = rcgen::DistinguishedName::new(); + params.custom_extensions.push(make_libp2p_extension( + identity_secret_key, + &certificate_keypair, + )?); + params + .self_signed(&certificate_keypair) + .expect("self signed certificate to be generated") + }; + + Ok((certificate.der().clone(), rustls_key)) +} + +/// Attempts to parse the provided bytes as a [`P2pCertificate`]. +/// +/// For this to succeed, the certificate must contain the specified extension and the signature must +/// match the embedded public key. +pub fn parse<'a>( + certificate: &'a rustls::pki_types::CertificateDer<'_>, +) -> Result<P2pCertificate<'a>, ParseError> { + let certificate = parse_unverified(certificate.as_ref())?; + + certificate.verify()?; + + Ok(certificate) +} + +/// An X.509 certificate with a libp2p-specific extension +/// is used to secure libp2p connections. +#[derive(Debug)] +pub struct P2pCertificate<'a> { + certificate: X509Certificate<'a>, + /// This is a specific libp2p Public Key Extension with two values: + /// * the public host key + /// * a signature performed using the private host key + extension: P2pExtension, +} + +/// The contents of the specific libp2p extension, containing the public host key +/// and a signature performed using the private host key. +#[derive(Debug)] +pub struct P2pExtension { + public_key: PublicKey, + /// This signature provides cryptographic proof that the peer was + /// in possession of the private host key at the time the certificate was signed. + signature: Signature, +} + +/// An error that occurs during certificate generation. +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct GenError(#[from] rcgen::Error); + +/// An error that occurs during certificate parsing. +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct ParseError(#[from] pub(crate) webpki::Error); + +/// An error that occurs during signature verification. +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct VerificationError(#[from] pub(crate) webpki::Error); + +/// Internal function that only parses but does not verify the certificate. +/// +/// Useful for testing but unsuitable for production. +fn parse_unverified(der_input: &[u8]) -> Result<P2pCertificate, webpki::Error> { + let x509 = X509Certificate::from_der(der_input) + .map(|(_rest_input, x509)| x509) + .map_err(|_| webpki::Error::BadDer)?; + + let p2p_ext_oid = der_parser::oid::Oid::from(&P2P_EXT_OID) + .expect("This is a valid OID of p2p extension; qed"); + + let mut libp2p_extension = None; + + for ext in x509.extensions() { + let oid = &ext.oid; + if oid == &p2p_ext_oid && libp2p_extension.is_some() { + // The extension was already parsed + return Err(webpki::Error::BadDer); + } + + if oid == &p2p_ext_oid { + let signed_key = + SignedKey::from_der(ext.value).map_err(|_| webpki::Error::ExtensionValueInvalid)?; + let public_key_raw = signed_key.public_key.as_bytes(); + let public_key = + PublicKey::try_from(public_key_raw).map_err(|_| webpki::Error::UnknownIssuer)?; + + let signature = Signature::from_slice(signed_key.signature.as_bytes()) + .map_err(|_| webpki::Error::UnknownIssuer)?; + let ext = P2pExtension { + public_key, + signature, + }; + libp2p_extension = Some(ext); + continue; + } + + if ext.critical { + // Endpoints MUST abort the connection attempt if the certificate + // contains critical extensions that the endpoint does not understand. + return Err(webpki::Error::UnsupportedCriticalExtension); + } + + // Implementations MUST ignore non-critical extensions with unknown OIDs. + } + + // The certificate MUST contain the libp2p Public Key Extension. + // If this extension is missing, endpoints MUST abort the connection attempt. + let extension = libp2p_extension.ok_or(webpki::Error::BadDer)?; + + let certificate = P2pCertificate { + certificate: x509, + extension, + }; + + Ok(certificate) +} + +fn make_libp2p_extension( + identity_secret_key: &SecretKey, + certificate_keypair: &rcgen::KeyPair, +) -> Result<rcgen::CustomExtension, rcgen::Error> { + // The peer signs the concatenation of the string `libp2p-tls-handshake:` + // and the public key that it used to generate the certificate carrying + // the libp2p Public Key Extension, using its private host key. + let signature = { + let mut msg = vec![]; + msg.extend(P2P_SIGNING_PREFIX); + msg.extend(certificate_keypair.public_key_der()); + + identity_secret_key.sign(&msg) + }; + + let public_key = identity_secret_key.public(); + let public_key_ref = OctetStringRef::new(&public_key.as_bytes()[..]) + .map_err(|_| rcgen::Error::CouldNotParseKeyPair)?; + let signature = signature.to_bytes(); + let signature_ref = + OctetStringRef::new(&signature).map_err(|_| rcgen::Error::CouldNotParseCertificate)?; + let key = SignedKey { + public_key: public_key_ref, + signature: signature_ref, + }; + + let mut extension_content = Vec::new(); + key.encode_to_vec(&mut extension_content).expect("vec"); + + // This extension MAY be marked critical. + let mut ext = rcgen::CustomExtension::from_oid_content(&P2P_EXT_OID, extension_content); + ext.set_criticality(true); + + Ok(ext) +} + +impl P2pCertificate<'_> { + /// The [`PublicKey`] of the remote peer. + pub fn peer_id(&self) -> PublicKey { + self.extension.public_key + } + + /// Verify the `signature` of the `message` signed by the secret key corresponding to the public key stored + /// in the certificate. + pub fn verify_signature( + &self, + signature_scheme: rustls::SignatureScheme, + message: &[u8], + signature: &[u8], + ) -> Result<(), VerificationError> { + let pk = self.public_key(signature_scheme)?; + pk.verify(message, signature) + .map_err(|_| webpki::Error::InvalidSignatureForPublicKey)?; + + Ok(()) + } + + /// Get a [`ring::signature::UnparsedPublicKey`] for this `signature_scheme`. + /// Return `Error` if the `signature_scheme` does not match the public key signature + /// and hashing algorithm or if the `signature_scheme` is not supported. + fn public_key( + &self, + signature_scheme: rustls::SignatureScheme, + ) -> Result<ring::signature::UnparsedPublicKey<&[u8]>, webpki::Error> { + use ring::signature; + use rustls::SignatureScheme::*; + + let current_signature_scheme = self.signature_scheme()?; + if signature_scheme != current_signature_scheme { + // This certificate was signed with a different signature scheme + return Err(webpki::Error::UnsupportedSignatureAlgorithmForPublicKey); + } + + let verification_algorithm: &dyn signature::VerificationAlgorithm = match signature_scheme { + ECDSA_NISTP256_SHA256 => &signature::ECDSA_P256_SHA256_ASN1, + ECDSA_NISTP384_SHA384 => &signature::ECDSA_P384_SHA384_ASN1, + ECDSA_NISTP521_SHA512 => { + // See https://github.com/briansmith/ring/issues/824 + return Err(webpki::Error::UnsupportedSignatureAlgorithm); + } + ED25519 => &signature::ED25519, + ED448 => { + // See https://github.com/briansmith/ring/issues/463 + return Err(webpki::Error::UnsupportedSignatureAlgorithm); + } + // No support for RSA + RSA_PKCS1_SHA256 | RSA_PKCS1_SHA384 | RSA_PKCS1_SHA512 | RSA_PSS_SHA256 + | RSA_PSS_SHA384 | RSA_PSS_SHA512 => { + return Err(webpki::Error::UnsupportedSignatureAlgorithm) + } + // Similarly, hash functions with an output length less than 256 bits + // MUST NOT be used, due to the possibility of collision attacks. + // In particular, MD5 and SHA1 MUST NOT be used. + RSA_PKCS1_SHA1 => return Err(webpki::Error::UnsupportedSignatureAlgorithm), + ECDSA_SHA1_Legacy => return Err(webpki::Error::UnsupportedSignatureAlgorithm), + Unknown(_) => return Err(webpki::Error::UnsupportedSignatureAlgorithm), + _ => return Err(webpki::Error::UnsupportedSignatureAlgorithm), + }; + let spki = &self.certificate.tbs_certificate.subject_pki; + let key = signature::UnparsedPublicKey::new( + verification_algorithm, + spki.subject_public_key.as_ref(), + ); + + Ok(key) + } + + /// This method validates the certificate according to libp2p TLS 1.3 specs. + /// The certificate MUST: + /// 1. be valid at the time it is received by the peer; + /// 2. use the NamedCurve encoding; + /// 3. use hash functions with an output length not less than 256 bits; + /// 4. be self signed; + /// 5. contain a valid signature in the specific libp2p extension. + fn verify(&self) -> Result<(), webpki::Error> { + use webpki::Error; + + // The certificate MUST have NotBefore and NotAfter fields set + // such that the certificate is valid at the time it is received by the peer. + if !self.certificate.validity().is_valid() { + return Err(Error::InvalidCertValidity); + } + + // Certificates MUST use the NamedCurve encoding for elliptic curve parameters. + // Similarly, hash functions with an output length less than 256 bits + // MUST NOT be used, due to the possibility of collision attacks. + // In particular, MD5 and SHA1 MUST NOT be used. + // Endpoints MUST abort the connection attempt if it is not used. + let signature_scheme = self.signature_scheme()?; + // Endpoints MUST abort the connection attempt if the certificate’s + // self-signature is not valid. + let raw_certificate = self.certificate.tbs_certificate.as_ref(); + let signature = self.certificate.signature_value.as_ref(); + // check if self signed + self.verify_signature(signature_scheme, raw_certificate, signature) + .map_err(|_| Error::SignatureAlgorithmMismatch)?; + + let subject_pki = self.certificate.public_key().raw; + + // The peer signs the concatenation of the string `libp2p-tls-handshake:` + // and the public key that it used to generate the certificate carrying + // the libp2p Public Key Extension, using its private host key. + let mut msg = vec![]; + msg.extend(P2P_SIGNING_PREFIX); + msg.extend(subject_pki); + + // This signature provides cryptographic proof that the peer was in possession + // of the private host key at the time the certificate was signed. + // Peers MUST verify the signature, and abort the connection attempt + // if signature verification fails. + let user_owns_sk = self + .extension + .public_key + .verify(&msg, &self.extension.signature) + .is_ok(); + if !user_owns_sk { + return Err(Error::UnknownIssuer); + } + + Ok(()) + } + + /// Return the signature scheme corresponding to [`AlgorithmIdentifier`]s + /// of `subject_pki` and `signature_algorithm` + /// according to <https://www.rfc-editor.org/rfc/rfc8446.html#section-4.2.3>. + fn signature_scheme(&self) -> Result<rustls::SignatureScheme, webpki::Error> { + // Certificates MUST use the NamedCurve encoding for elliptic curve parameters. + // Endpoints MUST abort the connection attempt if it is not used. + use oid_registry::*; + use rustls::SignatureScheme::*; + + let signature_algorithm = &self.certificate.signature_algorithm; + let pki_algorithm = &self.certificate.tbs_certificate.subject_pki.algorithm; + + if pki_algorithm.algorithm == OID_KEY_TYPE_EC_PUBLIC_KEY { + let signature_param = pki_algorithm + .parameters + .as_ref() + .ok_or(webpki::Error::BadDer)? + .as_oid() + .map_err(|_| webpki::Error::BadDer)?; + if signature_param == OID_EC_P256 + && signature_algorithm.algorithm == OID_SIG_ECDSA_WITH_SHA256 + { + return Ok(ECDSA_NISTP256_SHA256); + } + if signature_param == OID_NIST_EC_P384 + && signature_algorithm.algorithm == OID_SIG_ECDSA_WITH_SHA384 + { + return Ok(ECDSA_NISTP384_SHA384); + } + if signature_param == OID_NIST_EC_P521 + && signature_algorithm.algorithm == OID_SIG_ECDSA_WITH_SHA512 + { + return Ok(ECDSA_NISTP521_SHA512); + } + return Err(webpki::Error::UnsupportedSignatureAlgorithm); + } + + if signature_algorithm.algorithm == OID_SIG_ED25519 { + return Ok(ED25519); + } + if signature_algorithm.algorithm == OID_SIG_ED448 { + return Ok(ED448); + } + + Err(webpki::Error::UnsupportedSignatureAlgorithm) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sanity_check() { + let secret_key = SecretKey::generate(rand::thread_rng()); + + let (cert, _) = generate(&secret_key).unwrap(); + let parsed_cert = parse(&cert).unwrap(); + + assert!(parsed_cert.verify().is_ok()); + assert_eq!(secret_key.public(), parsed_cert.extension.public_key); + } +} diff --git a/content-discovery/tls/src/lib.rs b/content-discovery/tls/src/lib.rs new file mode 100644 index 0000000..ef22a33 --- /dev/null +++ b/content-discovery/tls/src/lib.rs @@ -0,0 +1,96 @@ +//! TLS configuration based on libp2p TLS specs. +//! +//! See <https://github.com/libp2p/specs/blob/master/tls/tls.md>. +//! Based on rust-libp2p/transports/tls + +use std::sync::Arc; + +use iroh_base::{PublicKey, SecretKey}; +use quinn::crypto::rustls::{NoInitialCipherSuite, QuicClientConfig, QuicServerConfig}; +use tracing::warn; + +use self::certificate::AlwaysResolvesCert; + +pub mod certificate; +mod verifier; + +/// Error for generating iroh p2p TLS configs. +#[derive(Debug, thiserror::Error)] +pub enum CreateConfigError { + /// Error generating the certificate. + #[error("Error generating the certificate")] + CertError(#[from] certificate::GenError), + /// Error creating QUIC config. + #[error("Error creating QUIC config")] + ConfigError(#[from] NoInitialCipherSuite), +} + +/// Create a TLS client configuration. +/// +/// If *keylog* is `true` this will enable logging of the pre-master key to the file in the +/// `SSLKEYLOGFILE` environment variable. This can be used to inspect the traffic for +/// debugging purposes. +pub fn make_client_config( + secret_key: &SecretKey, + remote_peer_id: Option<PublicKey>, + alpn_protocols: Vec<Vec<u8>>, + keylog: bool, +) -> Result<QuicClientConfig, CreateConfigError> { + let (certificate, secret_key) = certificate::generate(secret_key)?; + + let cert_resolver = Arc::new( + AlwaysResolvesCert::new(certificate, &secret_key) + .expect("Client cert key DER is valid; qed"), + ); + + let mut crypto = rustls::ClientConfig::builder_with_provider(Arc::new( + rustls::crypto::ring::default_provider(), + )) + .with_protocol_versions(verifier::PROTOCOL_VERSIONS) + .expect("version supported by ring") + .dangerous() + .with_custom_certificate_verifier(Arc::new( + verifier::Libp2pCertificateVerifier::with_remote_peer_id(remote_peer_id), + )) + .with_client_cert_resolver(cert_resolver); + crypto.alpn_protocols = alpn_protocols; + if keylog { + warn!("enabling SSLKEYLOGFILE for TLS pre-master keys"); + crypto.key_log = Arc::new(rustls::KeyLogFile::new()); + } + let config = crypto.try_into()?; + Ok(config) +} + +/// Create a TLS server configuration. +/// +/// If *keylog* is `true` this will enable logging of the pre-master key to the file in the +/// `SSLKEYLOGFILE` environment variable. This can be used to inspect the traffic for +/// debugging purposes. +pub fn make_server_config( + secret_key: &SecretKey, + alpn_protocols: Vec<Vec<u8>>, + keylog: bool, +) -> Result<QuicServerConfig, CreateConfigError> { + let (certificate, secret_key) = certificate::generate(secret_key)?; + + let cert_resolver = Arc::new( + AlwaysResolvesCert::new(certificate, &secret_key) + .expect("Server cert key DER is valid; qed"), + ); + + let mut crypto = rustls::ServerConfig::builder_with_provider(Arc::new( + rustls::crypto::ring::default_provider(), + )) + .with_protocol_versions(verifier::PROTOCOL_VERSIONS) + .expect("fixed config") + .with_client_cert_verifier(Arc::new(verifier::Libp2pCertificateVerifier::new())) + .with_cert_resolver(cert_resolver); + crypto.alpn_protocols = alpn_protocols; + if keylog { + warn!("enabling SSLKEYLOGFILE for TLS pre-master keys"); + crypto.key_log = Arc::new(rustls::KeyLogFile::new()); + } + let config = crypto.try_into()?; + Ok(config) +} diff --git a/content-discovery/tls/src/verifier.rs b/content-discovery/tls/src/verifier.rs new file mode 100644 index 0000000..8841d77 --- /dev/null +++ b/content-discovery/tls/src/verifier.rs @@ -0,0 +1,227 @@ +//! TLS 1.3 certificates and handshakes handling for libp2p +//! +//! This module handles a verification of a client/server certificate chain +//! and signatures allegedly by the given certificates. +//! +//! Based on rust-libp2p/transports/tls/src/verifier.rs originally licensed under MIT by Parity +//! Technologies (UK) Ltd. +use std::sync::Arc; + +use iroh_base::PublicKey; +use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + pki_types::CertificateDer as Certificate, + server::danger::{ClientCertVerified, ClientCertVerifier}, + CertificateError, DigitallySignedStruct, DistinguishedName, OtherError, PeerMisbehaved, + SignatureScheme, SupportedProtocolVersion, +}; + +use super::certificate; + +/// The protocol versions supported by this verifier. +/// +/// The spec says: +/// +/// > The libp2p handshake uses TLS 1.3 (and higher). +/// > Endpoints MUST NOT negotiate lower TLS versions. +pub static PROTOCOL_VERSIONS: &[&SupportedProtocolVersion] = &[&rustls::version::TLS13]; + +/// Implementation of the `rustls` certificate verification traits for libp2p. +/// +/// Only TLS 1.3 is supported. TLS 1.2 should be disabled in the configuration of `rustls`. +#[derive(Debug)] +pub struct Libp2pCertificateVerifier { + /// The peer ID we intend to connect to + remote_peer_id: Option<PublicKey>, +} + +/// libp2p requires the following of X.509 server certificate chains: +/// +/// - Exactly one certificate must be presented. +/// - The certificate must be self-signed. +/// - The certificate must have a valid libp2p extension that includes a +/// signature of its public key. +impl Libp2pCertificateVerifier { + pub fn new() -> Self { + Self { + remote_peer_id: None, + } + } + pub fn with_remote_peer_id(remote_peer_id: Option<PublicKey>) -> Self { + Self { remote_peer_id } + } + + /// Return the list of SignatureSchemes that this verifier will handle, + /// in `verify_tls12_signature` and `verify_tls13_signature` calls. + /// + /// This should be in priority order, with the most preferred first. + fn verification_schemes() -> Vec<SignatureScheme> { + vec![ + // TODO SignatureScheme::ECDSA_NISTP521_SHA512 is not supported by `ring` yet + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP256_SHA256, + // TODO SignatureScheme::ED448 is not supported by `ring` yet + SignatureScheme::ED25519, + // In particular, RSA SHOULD NOT be used. + ] + } +} + +impl ServerCertVerifier for Libp2pCertificateVerifier { + fn verify_server_cert( + &self, + end_entity: &Certificate, + intermediates: &[Certificate], + _server_name: &rustls::pki_types::ServerName, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> Result<ServerCertVerified, rustls::Error> { + let peer_id = verify_presented_certs(end_entity, intermediates)?; + + if let Some(ref remote_peer_id) = self.remote_peer_id { + // The public host key allows the peer to calculate the peer ID of the peer + // it is connecting to. Clients MUST verify that the peer ID derived from + // the certificate matches the peer ID they intended to connect to, + // and MUST abort the connection if there is a mismatch. + if remote_peer_id != &peer_id { + return Err(rustls::Error::PeerMisbehaved( + PeerMisbehaved::BadCertChainExtensions, + )); + } + } + + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + unreachable!("`PROTOCOL_VERSIONS` only allows TLS 1.3") + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + verify_tls13_signature(cert, dss.scheme, message, dss.signature()) + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + Self::verification_schemes() + } +} + +/// libp2p requires the following of X.509 client certificate chains: +/// +/// - Exactly one certificate must be presented. In particular, client +/// authentication is mandatory in libp2p. +/// - The certificate must be self-signed. +/// - The certificate must have a valid libp2p extension that includes a +/// signature of its public key. +impl ClientCertVerifier for Libp2pCertificateVerifier { + fn offer_client_auth(&self) -> bool { + true + } + + fn verify_client_cert( + &self, + end_entity: &Certificate, + intermediates: &[Certificate], + _now: rustls::pki_types::UnixTime, + ) -> Result<ClientCertVerified, rustls::Error> { + verify_presented_certs(end_entity, intermediates)?; + + Ok(ClientCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + unreachable!("`PROTOCOL_VERSIONS` only allows TLS 1.3") + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + verify_tls13_signature(cert, dss.scheme, message, dss.signature()) + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + Self::verification_schemes() + } + + fn root_hint_subjects(&self) -> &[DistinguishedName] { + &[][..] + } +} + +/// When receiving the certificate chain, an endpoint +/// MUST check these conditions and abort the connection attempt if +/// (a) the presented certificate is not yet valid, OR +/// (b) if it is expired. +/// Endpoints MUST abort the connection attempt if more than one certificate is received, +/// or if the certificate’s self-signature is not valid. +fn verify_presented_certs( + end_entity: &Certificate, + intermediates: &[Certificate], +) -> Result<PublicKey, rustls::Error> { + if !intermediates.is_empty() { + return Err(rustls::Error::General( + "libp2p-tls requires exactly one certificate".into(), + )); + } + + let cert = certificate::parse(end_entity)?; + + Ok(cert.peer_id()) +} + +fn verify_tls13_signature( + cert: &Certificate, + signature_scheme: SignatureScheme, + message: &[u8], + signature: &[u8], +) -> Result<HandshakeSignatureValid, rustls::Error> { + certificate::parse(cert)?.verify_signature(signature_scheme, message, signature)?; + + Ok(HandshakeSignatureValid::assertion()) +} + +impl From<certificate::ParseError> for rustls::Error { + fn from(certificate::ParseError(e): certificate::ParseError) -> Self { + use webpki::Error::*; + match e { + BadDer => rustls::Error::InvalidCertificate(CertificateError::BadEncoding), + e => { + rustls::Error::InvalidCertificate(CertificateError::Other(OtherError(Arc::new(e)))) + } + } + } +} +impl From<certificate::VerificationError> for rustls::Error { + fn from(certificate::VerificationError(e): certificate::VerificationError) -> Self { + use webpki::Error::*; + match e { + InvalidSignatureForPublicKey => { + rustls::Error::InvalidCertificate(CertificateError::BadSignature) + } + UnsupportedSignatureAlgorithm | UnsupportedSignatureAlgorithmForPublicKey => { + rustls::Error::InvalidCertificate(CertificateError::BadSignature) + } + e => { + rustls::Error::InvalidCertificate(CertificateError::Other(OtherError(Arc::new(e)))) + } + } + } +} diff --git a/h3-iroh/Cargo.toml b/h3-iroh/Cargo.toml index da5edf2..82d2a9d 100644 --- a/h3-iroh/Cargo.toml +++ b/h3-iroh/Cargo.toml @@ -15,7 +15,8 @@ http-body = { version = "1", optional = true } http-body-util = { version = "0.1", optional = true } hyper = { version = "1.5", optional = true } hyper-util = { version = "0.1", optional = true } -iroh = "0.29" +iroh = "0.30" +iroh-base = { version = "0.30", features = ["ticket"] } tokio = { version = "1", features = ["io-util"], default-features = false} tokio-util = "0.7" tower = { version = "0.5", optional = true } @@ -38,3 +39,7 @@ anyhow = "1" clap = { version = "4", features = ["derive"] } http = "1" tracing-subscriber = "0.3" + +[[example]] +name = "server-axum" +required-features = ["axum"] diff --git a/h3-iroh/examples/client.rs b/h3-iroh/examples/client.rs index 6c03009..f78564c 100644 --- a/h3-iroh/examples/client.rs +++ b/h3-iroh/examples/client.rs @@ -3,8 +3,8 @@ use std::str::FromStr; use anyhow::{bail, Context, Result}; use clap::Parser; -use iroh::ticket::NodeTicket; use iroh::NodeAddr; +use iroh_base::ticket::NodeTicket; use tokio::io::AsyncWriteExt; use tracing::info; diff --git a/h3-iroh/examples/server-axum.rs b/h3-iroh/examples/server-axum.rs index 5ffdc37..d7d1bd8 100644 --- a/h3-iroh/examples/server-axum.rs +++ b/h3-iroh/examples/server-axum.rs @@ -6,8 +6,7 @@ use anyhow::Result; use axum::response::Html; use axum::routing::get; use axum::Router; -use futures::StreamExt; -use iroh::ticket::NodeTicket; +use iroh_base::ticket::NodeTicket; use tracing::info; #[tokio::main] @@ -23,8 +22,8 @@ async fn main() -> Result<()> { info!("accepting connections on node: {}", ep.node_id()); // Wait for direct addresses and a RelayUrl before printing a NodeTicket. - ep.direct_addresses().next().await; - ep.watch_home_relay().next().await; + ep.direct_addresses().initialized().await?; + ep.home_relay().initialized().await?; let ticket = NodeTicket::new(ep.node_addr().await?); info!("node ticket: {ticket}"); info!("run: cargo run --example client -- iroh+h3://{ticket}/"); diff --git a/h3-iroh/examples/server.rs b/h3-iroh/examples/server.rs index 4f112e6..b4d5f74 100644 --- a/h3-iroh/examples/server.rs +++ b/h3-iroh/examples/server.rs @@ -8,13 +8,12 @@ use std::sync::Arc; use anyhow::{bail, Result}; use bytes::{Bytes, BytesMut}; use clap::Parser; -use futures::StreamExt; use h3::error::ErrorLevel; use h3::quic::BidiStream; use h3::server::RequestStream; use http::{Request, StatusCode}; use iroh::endpoint::{self, Incoming}; -use iroh::ticket::NodeTicket; +use iroh_base::ticket::NodeTicket; use tokio::fs::File; use tokio::io::AsyncReadExt; use tracing::{debug, error, field, info, info_span, Instrument, Span}; @@ -55,8 +54,8 @@ async fn main() -> Result<()> { info!("accepting connections on node: {}", ep.node_id()); // Wait for direct addresses and a RelayUrl before printing a NodeTicket. - ep.direct_addresses().next().await; - ep.watch_home_relay().next().await; + ep.direct_addresses().initialized().await?; + ep.home_relay().initialized().await?; let ticket = NodeTicket::new(ep.node_addr().await?); info!("node ticket: {ticket}"); info!("run e.g.: cargo run --example client -- iroh+h3://{ticket}/Cargo.toml"); diff --git a/iroh-dag-sync/Cargo.toml b/iroh-dag-sync/Cargo.toml index b13c5e6..7f69819 100644 --- a/iroh-dag-sync/Cargo.toml +++ b/iroh-dag-sync/Cargo.toml @@ -4,9 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -iroh-blobs = "0.29" -iroh-gossip = "0.29" -iroh = "0.29" +iroh-blobs = "0.30" +iroh-gossip = "0.30" +iroh = "0.30" +iroh-base = { version ="0.30", features = ["ticket"] } iroh-car = "0.5.0" redb = "2.1.1" clap = { version = "4.5.7", features = ["derive"] } @@ -24,6 +25,7 @@ iroh-quinn = "0.12" bytes = "1.6.0" hex = "0.4.3" ron = "0.8.1" +rand = "0.8.5" tracing = "0.1.40" serde_bytes = "0.11.14" ipld-core = "0.4.1" diff --git a/iroh-dag-sync/src/main.rs b/iroh-dag-sync/src/main.rs index 190178e..cdbdabc 100644 --- a/iroh-dag-sync/src/main.rs +++ b/iroh-dag-sync/src/main.rs @@ -6,8 +6,8 @@ use clap::Parser; use futures_lite::StreamExt; use ipld_core::codec::Links; use iroh::discovery::{dns::DnsDiscovery, pkarr::PkarrPublisher, ConcurrentDiscovery}; -use iroh::ticket::NodeTicket; use iroh::NodeAddr; +use iroh_base::ticket::NodeTicket; use iroh_blobs::store::{Map, MapEntry}; use iroh_blobs::{store::Store, BlobFormat}; use iroh_car::CarReader; @@ -19,7 +19,6 @@ use tables::{ReadOnlyTables, ReadableTables, Tables}; use tokio::io::AsyncWriteExt; use tokio_util::task::LocalPoolHandle; use traversal::{get_traversal, Traversal}; -use util::wait_for_relay; mod args; mod protocol; @@ -127,10 +126,13 @@ async fn main() -> anyhow::Result<()> { args::SubCommand::Node(args) => { let endpoint = create_endpoint(args.net.iroh_ipv4_addr, args.net.iroh_ipv6_addr).await?; - wait_for_relay(&endpoint).await?; + endpoint.home_relay().initialized().await?; let addr = endpoint.node_addr().await?; println!("Node id:\n{}", addr.node_id); - println!("Listening on {:#?}", addr.info); + println!( + "Listening on {:#?}, {:#?}", + addr.relay_url, addr.direct_addresses + ); println!("ticket:\n{}", NodeTicket::new(addr.clone())); while let Some(incoming) = endpoint.accept().await { let mut connecting = incoming.accept()?; diff --git a/iroh-dag-sync/src/util.rs b/iroh-dag-sync/src/util.rs index ee681d2..4398354 100644 --- a/iroh-dag-sync/src/util.rs +++ b/iroh-dag-sync/src/util.rs @@ -1,13 +1,5 @@ use anyhow::anyhow; -use iroh::{key::SecretKey, Endpoint}; - -// Wait for the endpoint to figure out its relay address. -pub async fn wait_for_relay(endpoint: &Endpoint) -> anyhow::Result<()> { - while endpoint.home_relay().is_none() { - tokio::time::sleep(std::time::Duration::from_millis(50)).await; - } - Ok(()) -} +use iroh::SecretKey; /// Get the secret key from a file or generate a new one. pub fn get_or_create_secret() -> anyhow::Result<SecretKey> { @@ -19,7 +11,7 @@ pub fn get_or_create_secret() -> anyhow::Result<SecretKey> { Ok(secret) } Err(_) => { - let secret = SecretKey::generate(); + let secret = SecretKey::generate(rand::thread_rng()); std::fs::write(FILENAME, secret.to_bytes())?; Ok(secret) } diff --git a/iroh-pkarr-naming-system/Cargo.toml b/iroh-pkarr-naming-system/Cargo.toml index 100c30f..d01d41d 100644 --- a/iroh-pkarr-naming-system/Cargo.toml +++ b/iroh-pkarr-naming-system/Cargo.toml @@ -8,8 +8,8 @@ edition = "2021" [dependencies] anyhow = "1.0.79" derive_more = "0.99.17" -iroh = "0.29" -iroh-blobs = "0.29" +iroh = "0.30" +iroh-blobs = "0.30" pkarr = { version = "1.0.1", features = ["async", "dht"] } tokio = "1.35.1" tokio-util = "0.7.12" diff --git a/iroh-pkarr-naming-system/src/lib.rs b/iroh-pkarr-naming-system/src/lib.rs index 8c6369d..3ab24d3 100644 --- a/iroh-pkarr-naming-system/src/lib.rs +++ b/iroh-pkarr-naming-system/src/lib.rs @@ -6,7 +6,7 @@ use std::{ }; use anyhow::Context; -use iroh::key::SecretKey; +use iroh::SecretKey; use iroh_blobs::HashAndFormat; use pkarr::{ dns::{ @@ -50,7 +50,7 @@ pub struct IPNS(Arc<Inner>); #[derive(Debug, Default)] struct Inner { pkarr: Arc<pkarr::PkarrClient>, - packets: Mutex<BTreeMap<iroh::key::PublicKey, (Record, AbortOnDropHandle<()>)>>, + packets: Mutex<BTreeMap<iroh::PublicKey, (Record, AbortOnDropHandle<()>)>>, } impl IPNS { @@ -89,10 +89,7 @@ impl IPNS { } /// Resolve a record for a public key. - pub async fn resolve( - &self, - public_key: iroh::key::PublicKey, - ) -> anyhow::Result<Option<Record>> { + pub async fn resolve(&self, public_key: iroh::PublicKey) -> anyhow::Result<Option<Record>> { let public_key = pkarr::PublicKey::try_from(*public_key.as_bytes()).context("invalid public key")?; let packet = self.0.pkarr.resolve(public_key).await; diff --git a/iroh-s3-bao-store/Cargo.toml b/iroh-s3-bao-store/Cargo.toml index 0a7c6b2..75700b1 100644 --- a/iroh-s3-bao-store/Cargo.toml +++ b/iroh-s3-bao-store/Cargo.toml @@ -21,8 +21,8 @@ flume = "0.11.0" futures-lite = "2.3" hex = "0.4.3" indicatif = "0.17.7" -iroh = "0.29" -iroh-blobs = "0.29" +iroh = "0.30" +iroh-blobs = "0.30" iroh-io = { version = "0.6", features = ["x-http"] } num_cpus = "1.16.0" rand = "0.8.5" diff --git a/iroh-s3-bao-store/src/main.rs b/iroh-s3-bao-store/src/main.rs index a9d2fce..f39fb7d 100644 --- a/iroh-s3-bao-store/src/main.rs +++ b/iroh-s3-bao-store/src/main.rs @@ -3,10 +3,11 @@ use clap::{Parser, Subcommand}; use indicatif::{ HumanBytes, HumanDuration, MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle, }; -use iroh::{key::SecretKey, ticket::BlobTicket, Endpoint, NodeAddr}; -use iroh_blobs::util::local_pool::LocalPool; +use iroh::{Endpoint, NodeAddr, SecretKey}; use iroh_blobs::{ provider::{self, handle_connection, CustomEventSender, EventSender}, + ticket::BlobTicket, + util::local_pool::LocalPool, BlobFormat, }; use iroh_io::{AsyncSliceReaderExt, HttpAdapter}; @@ -127,7 +128,7 @@ fn get_or_create_secret(print: bool) -> anyhow::Result<SecretKey> { match std::env::var("IROH_SECRET") { Ok(secret) => SecretKey::from_str(&secret).context("invalid secret"), Err(_) => { - let key = SecretKey::generate(); + let key = SecretKey::generate(rand::thread_rng()); if print { eprintln!("using secret key {}", key); } @@ -244,9 +245,7 @@ async fn serve_db( // wait for the endpoint to be ready let endpoint = builder.bind().await?; // wait for the endpoint to figure out its address before making a ticket - while endpoint.home_relay().is_none() { - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - } + endpoint.home_relay().initialized().await?; // make a ticket let addr = endpoint.node_addr().await?; on_addr(addr)?;