# Integrating iroh with Custom TLS Certificate Authorities: Implementation Guide

> Learn to integrate iroh with custom TLS certificate authorities. Configure TLS and X 509 certificates while maintaining raw public key verification for secure node authentication. Follow this implementation guide.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-07-11

---

**iroh uses raw public keys (RFC 7250) for end‑to‑end authentication, but you can configure custom TLS certificate authorities for the relay component using `CaRootsConfig` and `CaTlsConfig` from [`iroh-relay/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/tls.rs) to validate traditional X.509 certificates while preserving raw‑key verification for node authentication.**

The `n0-computer/iroh` library implements a hybrid TLS architecture that combines raw public key authentication with optional CA‑based TLS for infrastructure components. Understanding how to integrate custom TLS certificate authorities into this stack requires knowledge of where iroh uses traditional certificate validation versus its native raw‑key verification.

## Understanding iroh's Hybrid TLS Architecture

iroh splits TLS responsibilities across two layers: the core protocol uses raw public keys for direct peer authentication, while the relay infrastructure can optionally use conventional CA‑based TLS for transport security.

### Core Protocol: Raw Public Key Verification (RFC 7250)

In [`iroh/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls.rs), the `TlsConfig` struct creates a shared configuration that holds the secret key, certificate resolver, and custom verifier. This module builds both client and server QUIC configs using **rustls**, but with a critical distinction: it implements raw public key authentication instead of X.509 certificate chains.

The verification logic lives in [`iroh/src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/verifier.rs), which implements `rustls::client::danger::ServerCertVerifier` and `rustls::server::danger::ClientCertVerifier`. These verifiers check that the peer’s certificate encodes the expected Ed25519 public key (the peer’s iroh ID). The `ServerCertificateVerifier` decodes the DNS name (the peer’s iroh identifier) and validates that the presented SPKI matches the expected public key, guaranteeing end‑to‑end authentication without a CA.

The resolver in [`iroh/src/tls/resolver.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/resolver.rs) supplies a raw‑public‑key "certificate" via `ResolveRawPublicKeyCert`, converting an `iroh_base::SecretKey` into a `rustls::sign::CertifiedKey`.

### Infrastructure Layer: CA‑Based TLS for Relays

While the default iroh transport does not use a traditional CA hierarchy, the relay component (`iroh-relay`) can be configured with CA‑based TLS. The relay re‑exports two helper types from `iroh_relay::tls`:

*   `CaRootsConfig` – holds a set of trusted root certificates as a wrapper around `rustls::RootCertStore`.
*   `CaTlsConfig` – combines `CaRootsConfig` with the usual iroh TLS settings to build `rustls::ClientConfig` and `ServerConfig`.

These types are defined in [`iroh-relay/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/tls.rs). When you need to trust a private CA, you populate a `CaRootsConfig` with the CA’s PEM‑encoded certificates and pass the resulting `CaTlsConfig` to the relay. The rest of the iroh stack (raw‑key verification) remains unchanged; the custom CA only influences the TLS handshake performed by the relay.

## Implementing Custom CA Support

To integrate custom TLS certificate authorities, you configure the relay infrastructure while leaving the core iroh endpoint configuration unchanged.

### Step 1: Create a Custom Root Store

Load your private CA certificates into a `CaRootsConfig` using rustls’s `RootCertStore`:

```rust
use iroh_relay::tls::{CaRootsConfig, CaTlsConfig};
use rustls::RootCertStore;
use std::{fs, sync::Arc};

// Load PEM‑encoded CA certificates
let ca_pem = fs::read_to_string("my_ca.pem")?;
let mut roots = RootCertStore::empty();
roots.add_parsable_certificates(&rustls_pemfile::certs(&mut ca_pem.as_bytes()))?;

// Build the CA config
let ca_roots = CaRootsConfig::new(Arc::new(roots));

```

The `CaRootsConfig` wraps the `rustls::RootCertStore` and will be used to validate certificates presented by the relay or when the relay acts as a client to other TLS endpoints.

### Step 2: Configure the Relay Server

Build a `CaTlsConfig` and pass it to the relay builder:

```rust
use iroh_relay::RelayBuilder;

let ca_tls = CaTlsConfig::builder()
    .with_ca_roots(ca_roots)
    .with_keylog(true)          // optional: enable SSLKEYLOGFILE debugging
    .with_crypto_provider(rustls::crypto::CryptoProvider::default())
    .build()?;

let relay = RelayBuilder::new()
    .tls_config(ca_tls)
    .bind("0.0.0.0:443")?
    .run()
    .await?;

```

The relay now validates TLS connections against your private CA before passing the raw‑key‑verified stream to the iroh core.

### Step 3: Connect Clients Through the Protected Relay

Your iroh endpoint continues to use raw‑key authentication via `TlsConfig::make_client_config()` and `make_server_config()`, but connects through the CA‑protected relay:

```rust
use iroh::{Endpoint, tls::TlsConfig};
use iroh_base::SecretKey;
use std::sync::Arc;

let secret_key = SecretKey::generate();
let tls_cfg = TlsConfig::new(
    secret_key, 
    8, 
    Arc::new(rustls::crypto::CryptoProvider::default())
);

let endpoint = Endpoint::builder()
    .tls_config(tls_cfg)
    .relay("tls://my-relay.example.com")   // TLS scheme uses the relay's TLS endpoint
    .bind()?
    .run()
    .await?;

```

The client still authenticates the remote peer with raw public keys (handled by [`verifier.rs`](https://github.com/n0-computer/iroh/blob/main/verifier.rs)), while the underlying TLS tunnel is validated against the private CA you supplied to the relay.

## Complete Working Example

This example demonstrates both the relay configuration with custom CA support and the client connection:

```rust
// --- relay/main.rs ---------------------------------------------------------
use iroh_relay::{RelayBuilder, tls::{CaRootsConfig, CaTlsConfig}};
use rustls::{RootCertStore, crypto::CryptoProvider};
use std::{fs, sync::Arc};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Load custom CA
    let ca_pem = fs::read_to_string("my_ca.pem")?;
    let mut roots = RootCertStore::empty();
    roots.add_parsable_certificates(&rustls_pemfile::certs(&mut ca_pem.as_bytes()))?;
    let ca_cfg = CaRootsConfig::new(Arc::new(roots));

    // Build TLS config for the relay
    let tls = CaTlsConfig::builder()
        .with_ca_roots(ca_cfg)
        .with_crypto_provider(CryptoProvider::default())
        .build()?;

    // Run the relay with custom CA validation
    RelayBuilder::new()
        .tls_config(tls)
        .bind("0.0.0.0:443")?
        .run()
        .await?;
    Ok(())
}

// --- client/main.rs --------------------------------------------------------
use iroh::{Endpoint, tls::TlsConfig};
use iroh_base::SecretKey;
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Generate iroh node key (raw public key identity)
    let secret = SecretKey::generate();

    // Create TLS config with raw-key verification
    let tls_cfg = TlsConfig::new(
        secret.clone(), 
        8, 
        Arc::new(rustls::crypto::CryptoProvider::default())
    );

    // Connect via the custom-CA-protected relay
    let ep = Endpoint::builder()
        .tls_config(tls_cfg)
        .relay("tls://my-relay.example.com")
        .bind()?
        .run()
        .await?;

    // Use ep normally - all peer authentication uses raw public keys
    Ok(())
}

```

This architecture ensures that the relay validates the TLS layer with your private CA while the iroh core maintains its end‑to‑end security through raw‑public‑key verification.

## Summary

*   **Raw public key verification** is implemented in [`iroh/src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/verifier.rs) and [`iroh/src/tls/resolver.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/resolver.rs), using `ServerCertVerifier` to check Ed25519 public keys against iroh node IDs.
*   **Custom CA integration** happens at the relay layer via [`iroh-relay/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/tls.rs), specifically through `CaRootsConfig` (wrapping `rustls::RootCertStore`) and `CaTlsConfig`.
*   **TLS 1.3 only** – Both client and server configurations force TLS 1.3 (`PROTOCOL_VERSIONS`), with TLS 1.2 explicitly rejected (`verify_tls12_signature` returns `Tls12NotOffered`).
*   **Zero‑RTT support** – `TlsConfig` creates a `ClientSessionMemoryCache` (`DEFAULT_MAX_TLS_TICKETS`) and enables early data (`enable_early_data = true`), which custom CA configuration does not interfere with.
*   **Separation of concerns** – The relay validates CA‑based TLS for transport security, while iroh endpoints validate raw keys for application‑layer authentication.

## Frequently Asked Questions

### Does iroh require a custom CA for peer‑to‑peer connections?

No. Peer‑to‑peer connections in iroh use **raw public keys** (RFC 7250) for authentication, encoded via `ResolveRawPublicKeyCert` in [`iroh/src/tls/resolver.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/resolver.rs). No certificate authority is involved in validating the identity of remote nodes; verification happens by comparing the presented Ed25519 public key against the expected iroh node ID in [`verifier.rs`](https://github.com/n0-computer/iroh/blob/main/verifier.rs).

### Can I use traditional X.509 certificates instead of raw public keys for iroh nodes?

The default iroh transport in [`iroh/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls.rs) is designed specifically for raw public key authentication. While the underlying rustls library supports X.509, iroh’s `TlsConfig` and [`verifier.rs`](https://github.com/n0-computer/iroh/blob/main/verifier.rs) implement custom verification logic that expects raw public keys. To use traditional X.509 certificates, you would need to implement custom verifier traits outside the standard iroh TLS configuration.

### How does custom CA configuration affect connection performance?

Custom CA configuration does not impact the **Zero‑RTT** (0‑RTT) support or connection resumption mechanisms. The `TlsConfig` maintains a `ClientSessionMemoryCache` with `DEFAULT_MAX_TLS_TICKETS` and keeps `enable_early_data` set to true. The custom CA only affects the initial trust anchor validation during the TLS handshake performed by the relay, not the cryptographic performance of the connection.

### What security properties does the raw‑public‑key verifier enforce?

The `ServerCertVerifier` implementation in [`iroh/src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/verifier.rs) enforces that the peer’s certificate contains an Ed25519 public key matching the expected iroh ID. It rejects any certificate that does not encode the specific raw public key, effectively preventing man‑in‑the‑middle attacks without requiring a CA hierarchy. This provides **end‑to‑end authentication** independent of the relay’s TLS configuration.