How Iroh Uses EndpointId as a TLS Identity for Mutual Authentication

Iroh authenticates peers over its QUIC transport by treating each endpoint's 32-byte public key (EndpointId) as a TLS identity, encoding it as a DNS name for Server Name Indication (SNI) and embedding it directly as a Raw Public Key (RPK) during the TLS handshake to achieve certificate-less mutual authentication.

The iroh networking library from n0-computer eliminates traditional certificate infrastructure by using EndpointId as a TLS identity for mutual authentication. Instead of relying on X.509 certificates, iroh encodes each endpoint's public key as a deterministic DNS name and leverages Raw Public Key (RPK) TLS extensions per RFC 7250. This approach enables cryptographic peer verification without certificate authorities or PKI overhead.

Encoding EndpointId as a DNS Name for SNI

Iroh derives a deterministic server name from the EndpointId to facilitate connection routing and identity indication during the TLS handshake.

Base32 DNS-SEC Encoding

The iroh/src/tls/name.rs module encodes the 32-byte public key (EndpointId) using base-32 DNS-SEC encoding and appends the constant suffix .iroh.invalid. This creates a reserved, non-resolvable domain name that serves as the Server Name Indication (SNI) value in TLS client configurations.

For example, an EndpointId encodes to a string like:


7dl2ff6emqi2qol3l382krodedij45bn3nh479hqo14a32qpr8kg.iroh.invalid

Implementation in name.rs

The encoding implementation constructs the DNS name by formatting the base32-encoded bytes with the iroh-specific TLD:

// iroh/src/tls/name.rs
format!("{}.iroh.invalid", BASE32_DNSSEC.encode(endpoint_id.as_bytes()))

This deterministic mapping ensures that the SNI value uniquely identifies the expected endpoint while remaining within valid DNS character constraints.

Raw Public Key TLS Handshake

Rather than transmitting full X.509 certificate chains, iroh leverages Raw Public Keys (RPK) as defined in RFC 7250. This allows the endpoint's public key to function directly as its authentication credential.

RFC 7250 Compliance

Both client and server generate rustls::ClientConfig and rustls::ServerConfig instances that include a rustls::RootCertStore populated with the peer's EndpointId as the sole trusted credential. During the handshake, the raw public key transmits in a TLS extension rather than a traditional certificate message.

Verifier Implementation

The custom verifier in iroh/src/tls/verifier.rs implements verify_server_cert and verify_client_cert to extract the presented raw public key from the TLS session. It compares this key against the expected EndpointId and aborts the connection immediately if they do not match cryptographically.

This verification occurs during the handshake phase, preventing unauthorized peers from establishing connections before application-level data transmits.

Bidirectional Authentication Flow

Because both connection endpoints supply raw public keys, iroh achieves mutual authentication without additional handshake rounds. Each side independently verifies the other's identity.

Extracting the Peer EndpointId

After the TLS handshake completes, the peer_endpoint_id() function in iroh/src/endpoint/connection.rs extracts the remote's identity from the connection's peer certificates. The implementation retrieves the first (and only) certificate—which contains the raw public key—and parses it into an EndpointId:

// iroh/src/endpoint/connection.rs (approx. line 350)
let certs = conn.peer_certificates()?;
let raw_key = certs[0].as_ref(); // RawPublicKey
EndpointId::from_bytes(raw_key).ok()

Routing Table Integration

The endpoint records the verified remote EndpointId in its internal RemoteMap routing tables. This ensures that subsequent packets route to the correct peer based on cryptographic identity rather than IP address, enabling NAT traversal and connection migration while maintaining strong authentication guarantees.

Implementation Examples

Creating a TLS Client Configuration

Configure a rustls client that uses your EndpointId as its TLS identity and sets the appropriate SNI:

use iroh_base::EndpointId;
use iroh::tls::{self, name};
use std::sync::Arc;

fn client_config(my_id: EndpointId) -> rustls::ClientConfig {
    // Encode our own EndpointId as the SNI name
    let server_name = name::encode(my_id);
    let mut cfg = rustls::ClientConfig::builder()
        .with_safe_defaults()
        .with_custom_certificate_verifier(
            Arc::new(tls::verifier::Verifier::default())
        )
        .with_no_client_auth(); // RPK added separately

    // Set the SNI to indicate which EndpointId we expect
    cfg.set_sni(server_name);
    cfg
}

Verifying Peer Identity Post-Handshake

Retrieve the authenticated peer's EndpointId from an established connection:

use rustls::Connection;
use iroh_base::EndpointId;

fn peer_endpoint_id(conn: &Connection) -> Option<EndpointId> {
    // The verifier already confirmed the raw key matches a known EndpointId
    let certs = conn.peer_certificates()?;
    let raw_key = certs[0].as_ref(); // First (and only) RawPublicKey
    EndpointId::from_bytes(raw_key).ok()
}

Encoding and Decoding DNS Names

Convert between EndpointId and the DNS name format used for SNI:

use iroh_base::EndpointId;
use iroh::tls::name;

let id: EndpointId = /* ... */;
let dns_name = name::encode(id);
assert_eq!(Some(id), name::decode(&dns_name));

Summary

  • EndpointId serves dual purpose: It functions as both the network address (via DNS name encoding) and the cryptographic authentication credential (via Raw Public Key TLS).
  • Deterministic encoding: The iroh/src/tls/name.rs module encodes the 32-byte public key with base32 DNS-SEC and appends .iroh.invalid for SNI values.
  • Certificate-less verification: The verifier in iroh/src/tls/verifier.rs implements RFC 7250 to validate raw public keys directly against expected EndpointId values.
  • Post-handshake extraction: The peer_endpoint_id() function in iroh/src/endpoint/connection.rs retrieves the authenticated peer's identity for routing table management.
  • Mutual authentication: Both client and server present and verify raw public keys, eliminating the need for X.509 certificates or PKI infrastructure.

Frequently Asked Questions

What is an EndpointId in iroh?

An EndpointId is a 32-byte public key that uniquely identifies an iroh endpoint. It serves as the fundamental identity primitive for the entire networking stack, acting as both the addressing mechanism and the cryptographic root of trust for TLS authentication.

Why does iroh use .iroh.invalid as a domain suffix?

The .iroh.invalid suffix is a reserved, non-resolvable top-level domain that ensures the encoded EndpointId conforms to DNS naming conventions required for TLS SNI fields. This guarantees compatibility with TLS libraries while preventing accidental resolution as a real domain name.

How does iroh verify the peer's identity without certificates?

Iroh implements RFC 7250 Raw Public Key support, where the EndpointId (the public key itself) transmits directly in the TLS handshake. The custom verifier in iroh/src/tls/verifier.rs compares this raw key against the expected EndpointId bytes, rejecting the connection if they do not match perfectly.

Can iroh interoperate with standard TLS/X.509 infrastructure?

No, iroh's mutual authentication requires both endpoints to support Raw Public Key TLS extensions. Standard TLS implementations that expect X.509 certificates cannot complete handshakes with iroh endpoints, as the protocol intentionally avoids certificate chains to reduce complexity and attack surface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →