How iroh Implements End-to-End Encryption Without Certificates

Iroh achieves end-to-end encryption without certificates by using raw public keys (RFC 7250) embedded directly in the TLS handshake, binding each endpoint's Ed25519 public key to its identity and eliminating the need for X.509 infrastructure.

Traditional secure communication relies on X.509 certificate chains issued by trusted certificate authorities, but the iroh networking library takes a fundamentally different approach. By leveraging the raw public key extension for TLS, iroh enables direct peer authentication using cryptographic keys as identities, providing end-to-end encryption without certificates while maintaining the security guarantees of TLS 1.3 over QUIC.

The Raw Public Key Architecture

Endpoint Identity Derived from Cryptographic Keys

In iroh, every endpoint possesses a long-term SecretKey that generates a corresponding PublicKey. This public key serves dual purposes: it functions as the cryptographic identity (EndpointId) and as the raw public key presented during the TLS handshake. As implemented in n0-computer/iroh, the public key is derived from the secret key and embedded directly into the TLS configuration without any X.509 wrapping.

Core TLS Components

The encryption stack builds upon rustls and QUIC, with the central TlsConfig struct (defined in src/tls.rs) orchestrating the certificate-less architecture. When an Endpoint is created via Endpoint::builder(), the builder instantiates a TlsConfig that creates two critical components:

  1. ResolveRawPublicKeyCert – A custom certificate resolver located in src/tls/resolver.rs that presents the endpoint's public key as a self-signed structure containing only the raw SubjectPublicKeyInfo (SPKI). It implements both rustls::client::ResolvesClientCert and rustls::server::ResolvesServerCert while advertising only_raw_public_keys = true.
  2. ServerCertificateVerifier / ClientCertificateVerifier – Custom verification logic in src/tls/verifier.rs that rejects traditional certificate chains and instead compares the peer's raw public-key SPKI bytes against the expected endpoint ID.

How the TLS Handshake Works

Certificate Resolution with Raw Public Keys

During the TLS handshake, the ResolveRawPublicKeyCert resolver constructs a minimal "certificate" containing only the raw public key SPKI structure. This implements the RFC 7250 standard for raw public keys, allowing the endpoint to present its identity without X.509 certificates. The resolver sets only_raw_public_keys = true, forcing the handshake to use this certificate-less mode.

Peer Verification Without Certificate Authorities

The custom verifiers in src/tls/verifier.rs (lines 41-70) perform authentication by extracting the expected endpoint ID from the TLS SNI (Server Name Indication) field using name::encode and name::decode. The verifier then compares the peer's presented SPKI bytes against the public key derived from the expected endpoint ID. Because the verifiers require raw public keys (requires_raw_public_keys = true), TLS 1.2 is disabled and only TLS 1.3 is permitted via the PROTOCOL_VERSIONS configuration.

The cryptographic signature verification delegates to iroh's own PublicKey and Signature types using Ed25519Dalek, ensuring the same Ed25519 key material used for identity also secures the handshake.

Code Implementation Examples

Creating an Endpoint with Raw-Key TLS

The simplest way to utilize iroh's end-to-end encryption without certificates is through the Endpoint builder, which automatically configures the raw public key TLS stack:

use iroh::{Endpoint, SecretKey, endpoint::presets};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Generate a fresh secret key – the public part becomes the endpoint ID.
    let secret_key = SecretKey::generate();

    // Build an endpoint. The builder internally creates a `TlsConfig`
    // that uses raw public keys (see src/tls.rs).
    let endpoint = Endpoint::builder(presets::N0)
        .secret_key(secret_key)          // <-- raw‑key TLS config is derived here
        .alpns(vec![b"my/alpn".to_vec()]) // Application protocol identifier
        .relay_mode(iroh::RelayMode::Default)
        .bind()
        .await?;

    // The endpoint is now ready to talk securely to any other iroh endpoint.
    // The TLS handshake will authenticate the peer using its public key.
    Ok(())
}

Manual TLS Configuration

For advanced use cases, you can manually construct the TlsConfig that powers the certificate-less encryption:

use iroh::tls::{TlsConfig, DEFAULT_MAX_TLS_TICKETS};
use iroh_base::SecretKey;
use std::sync::Arc;
use rustls::crypto::CryptoProvider;

// Assume a `SecretKey` has already been generated.
let secret_key = SecretKey::generate();
let crypto = CryptoProvider::default(); // Any rustls crypto provider

// Build the shared TLS configuration.
let tls_cfg = TlsConfig::new(secret_key, DEFAULT_MAX_TLS_TICKETS, Arc::new(crypto));

// Produce a QUIC client config that will only accept raw public keys.
let quic_client = tls_cfg.make_client_config(/*keylog=*/false)?;

Verification Logic Deep Dive

The authentication mechanism in src/tls/verifier.rs extracts the endpoint ID from the SNI and validates the public key:

// Inside ServerCertificateVerifier::verify_server_cert (src/tls/verifier.rs)
let dns_name = match server_name {
    rustls::pki_types::ServerName::DnsName(d) => d,
    _ => return Err(rustls::Error::UnsupportedNameType),
};
let remote_id = name::decode(dns_name.as_ref())?; // endpoint ID from SNI
let spki = SubjectPublicKeyInfoDer::from(end_entity.as_ref());
let expected_spki = rustls::sign::public_key_to_spki(
    &webpki_types::alg_id::ED25519,
    remote_id.as_bytes(),
);
if spki != expected_spki {
    return Err(rustls::Error::InvalidCertificate(...));
}
Ok(ServerCertVerified::assertion())

Summary

  • Iroh eliminates X.509 certificates by using raw public keys (RFC 7250) embedded directly in the TLS handshake.
  • The SecretKey generates a PublicKey that serves as both the cryptographic identity (EndpointId) and the authentication credential.
  • ResolveRawPublicKeyCert in src/tls/resolver.rs presents the raw SPKI structure instead of traditional certificates.
  • Custom verifiers in src/tls/verifier.rs authenticate peers by matching raw public-key SPKI bytes to the expected endpoint ID extracted from TLS SNI.
  • The architecture mandates TLS 1.3 over QUIC, using Ed25519 signatures for handshake security without external certificate authorities.

Frequently Asked Questions

Does using raw public keys instead of certificates reduce security?

No. Raw public keys provide the same cryptographic guarantees as X.509 certificates because they use identical underlying key material. By eliminating the certificate chain verification, iroh removes the complexity and trust assumptions of external CAs while maintaining the authentication strength of Ed25519 public keys. The peer verification logic in src/tls/verifier.rs ensures that the presented public key matches the expected endpoint ID exactly.

How does iroh prevent man-in-the-middle attacks without certificates?

Iroh prevents MITM attacks through direct key authentication. When connecting to a peer, the endpoint knows the expected EndpointId (the public key) in advance. The custom ServerCertificateVerifier extracts the endpoint ID from the TLS SNI and verifies that the raw public key presented during the handshake matches this expected value. This binds the connection cryptographically to the specific peer without requiring certificate chains.

Why does iroh require TLS 1.3 for raw public keys?

Raw public keys require requires_raw_public_keys = true in the verifier configuration, which explicitly disables TLS 1.2 and earlier versions. TLS 1.3 provides improved handshake security and is required by the raw public key extension implementation in rustls. The PROTOCOL_VERSIONS configuration in src/tls.rs restricts the handshake to TLS 1.3 only, ensuring modern cryptographic standards.

Can iroh interoperate with standard TLS clients that use certificates?

No. Because iroh configures only_raw_public_keys = true in the ResolveRawPublicKeyCert resolver and requires raw public keys in the verifiers, it explicitly rejects traditional X.509 certificate chains. This creates a certificate-less ecosystem where all participants must use raw public keys for authentication, ensuring consistency across the iroh network.

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 →