# How iroh Implements End-to-End Encryption Without Certificates

> Discover how Iroh implements end-to-end encryption without certificates using raw public keys and embedded Ed25519 identity for secure communication. Learn more.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: deep-dive
- Published: 2026-06-21

---

**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`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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:

```rust
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:

```rust
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`](https://github.com/n0-computer/iroh/blob/main/src/tls/verifier.rs) extracts the endpoint ID from the SNI and validates the public key:

```rust
// 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`](https://github.com/n0-computer/iroh/blob/main/src/tls/resolver.rs) presents the raw SPKI structure instead of traditional certificates.
- **Custom verifiers** in [`src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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.