# How iroh Validates Peer Identity and Authenticates: A Deep Dive into TLS 1.3 Raw Public Key Verification

> Discover how iroh authenticates peers using TLS 1.3 raw public key verification. Learn about Ed25519, DNS identity encoding, and CA-free public key matching for secure P2P connections.

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

---

**iroh authenticates peers using TLS 1.3 with raw public keys (Ed25519), encoding the peer identity in DNS names and verifying the certificate's SubjectPublicKeyInfo (SPKI) matches the expected public key without relying on certificate authorities.**

The n0-computer/iroh repository implements a peer-to-peer networking stack that eliminates traditional certificate authorities by using **raw public key authentication**. Understanding how iroh validates peer identity requires examining its custom TLS verifiers that perform cryptographic checks against Ed25519 keys during the handshake process.

## The Foundation: TLS 1.3 with Raw Public Keys

Unlike traditional TLS implementations that rely on X.509 certificate chains and certificate authorities, iroh uses **raw public keys** as defined in RFC 7250. This approach removes the complexity of certificate management while maintaining cryptographic security. The system implements two custom `rustls` verifiers in [`iroh/src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/verifier.rs):

- **`ServerCertificateVerifier`** – Validates that the server certificate encodes the exact public key of the expected peer
- **`ClientCertificateVerifier`** – Ensures the client presents a raw public key that matches the expected peer identity

Both verifiers explicitly require raw public keys by returning `true` from `requires_raw_public_keys()` and disable TLS 1.2 by making `verify_tls12_signature` return fatal errors.

## Encoding Peer Identity in DNS Names

### The `iroh-<hex>` Format

Before the TLS handshake begins, iroh encodes the peer's 32-byte Ed25519 public key into a DNS-compatible string format. The encoding follows the pattern:

```

iroh-<hex-encoded-public-key>

```

This format allows the public key to be transmitted as part of the TLS ServerName extension while maintaining human readability.

### Decoding in [`name.rs`](https://github.com/n0-computer/iroh/blob/main/name.rs)

In [`iroh/src/tls/name.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/name.rs), the `decode` function parses the DNS name back into a `PublicKey` type defined in [`iroh-base/src/key.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/key.rs). This decoded key becomes the **expected identity** for the TLS handshake. The module also provides an `encode` function that converts a `PublicKey` into the DNS name format, ensuring consistency across the codebase.

## Server-Side Identity Verification

### Extracting the Expected Peer ID

The `ServerCertificateVerifier` implementation in [`iroh/src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/verifier.rs) performs a three-step validation process:

1. **DNS Name Decoding** – The verifier extracts the `ServerName` from the TLS handshake and decodes it using the logic in [`name.rs`](https://github.com/n0-computer/iroh/blob/main/name.rs) to obtain the expected `PublicKey`
2. **Certificate Chain Validation** – The verifier rejects any certificate chains with intermediate certificates. iroh only accepts a single self-signed certificate containing the raw public key
3. **SPKI Comparison** – The certificate's SubjectPublicKeyInfo (SPKI) is extracted and compared byte-for-byte against the SPKI derived from the expected public key (`remote_public_spki`)

If the SPKI values match exactly, the server returns `ServerCertVerified::assertion()`, indicating successful authentication.

### Implementation Example

The following code demonstrates how to configure a server with the custom client certificate verifier:

```rust
use iroh::tls::{ClientCertificateVerifier};
use rustls::server::ServerConfig;

let server_config = ServerConfig::builder()
    .with_safe_defaults()
    .with_custom_certificate_verifier(
        Arc::new(ClientCertificateVerifier::default())
    )
    .with_single_cert(cert_chain, private_key)
    .expect("failed to create server config");

```

## Client-Side Authentication

### Raw Key Verification

The `ClientCertificateVerifier` (also in [`iroh/src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/verifier.rs)) mirrors the server logic but for incoming client connections. It enforces the same restrictions:

- **No intermediate certificates** – Only accepts end-entity certificates with raw public keys
- **TLS 1.3 only** – Explicitly rejects TLS 1.2 signature verification attempts
- **Raw key requirement** – Returns `true` from `requires_raw_public_keys()`

### Signature Validation

Unlike traditional client certificate verification, iroh does not re-validate the client certificate itself after the handshake. Instead, it relies on the TLS 1.3 handshake process to verify signatures using the raw public key via `verify_tls13_signature_with_raw_key`. Upon successful verification, it returns `ClientCertVerified::assertion()`.

To configure a client with server verification:

```rust
use iroh::tls::{ServerCertificateVerifier};
use rustls::client::ClientConfig;

let client_config = ClientConfig::builder()
    .with_safe_defaults()
    .with_custom_certificate_verifier(
        Arc::new(ServerCertificateVerifier::default())
    )
    .with_no_client_auth();

```

## The Cryptographic Verification Layer

### Ed25519 Signature Verification

The actual cryptographic verification is handled by the `Ed25519Dalek` struct in [`iroh/src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/verifier.rs), which implements the `SignatureVerificationAlgorithm` trait. This wrapper converts the raw key and signature into iroh's native `PublicKey` and `Signature` types (defined in [`iroh-base/src/key.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/key.rs)) and calls `public_key.verify(message, &signature)`.

The verification uses `webpki_types::verify_tls13_signature_with_raw_key` as the underlying mechanism, ensuring compatibility with the TLS 1.3 specification while using Ed25519 for the cryptographic operations.

## Integration with the iroh Networking Stack

The TLS configuration is constructed in [`iroh/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls.rs), which wires the custom verifiers into `rustls::ClientConfig` and `rustls::ServerConfig`. When connections are established via [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) (through the `connect` and `listen` methods), the TLS session automatically runs these verifiers.

Once the handshake completes successfully, the verified public key is cached in the session and used for subsequent cryptographic operations, such as signing messages in the QUIC transport layer. This ensures that **only peers possessing the correct Ed25519 private key can complete the handshake** and participate in the network.

## Summary

- iroh uses **TLS 1.3 with raw public keys** (RFC 7250) instead of traditional X.509 certificate chains
- Peer identities are encoded as DNS names in the format `iroh-<hex-encoded-public-key>` and decoded in [`iroh/src/tls/name.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/name.rs)
- The `ServerCertificateVerifier` in [`iroh/src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/verifier.rs) validates servers by comparing the certificate's SPKI against the expected public key extracted from the DNS name
- The `ClientCertificateVerifier` performs equivalent validation for client connections
- **Ed25519** signatures are verified using `webpki_types::verify_tls13_signature_with_raw_key` via the `Ed25519Dalek` wrapper
- No certificate authorities are involved; authentication relies entirely on direct public key comparison during the TLS handshake

## Frequently Asked Questions

### Does iroh use traditional X.509 certificates for authentication?

No. iroh uses **raw public keys** (RFC 7250) rather than X.509 certificate chains. The system encodes the Ed25519 public key directly into a self-signed certificate and validates it by comparing the SPKI against the expected peer identity, eliminating the need for certificate authorities entirely.

### What cryptographic algorithm does iroh use for peer identity verification?

iroh uses **Ed25519** for all peer identity operations. The 32-byte public keys are encoded in DNS names, and signatures are verified using the `Ed25519Dalek` implementation in [`iroh/src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/verifier.rs), which wraps the Ed25519 verification algorithm for use with `rustls`.

### How does iroh prevent man-in-the-middle attacks without certificate authorities?

iroh prevents MITM attacks by requiring **exact byte-for-byte SPKI matches** between the certificate presented during the TLS handshake and the expected public key encoded in the DNS name. Since the expected peer ID is known beforehand (typically distributed through the application layer or pre-shared), an attacker cannot substitute their own certificate without detection.

### Can iroh interoperate with standard TLS implementations?

No, iroh cannot interoperate with standard TLS implementations that expect X.509 certificate chains. Both the client and server must support **raw public keys** and TLS 1.3. The custom verifiers in [`iroh/src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/verifier.rs) explicitly require raw public key support and disable TLS 1.2, making interoperability with traditional TLS stacks impossible.