# dnscrypt

[![Crates.io](https://img.shields.io/crates/v/dnscrypt.svg)](https://crates.io/crates/dnscrypt)
[![Documentation](https://docs.rs/dnscrypt/badge.svg)](https://docs.rs/dnscrypt)
[![License](https://img.shields.io/crates/l/dnscrypt.svg)](https://codeberg.org/hacer-bark/dnscrypt#license)

A pure-Rust, high-assurance implementation of the **DNSCrypt v2 client protocol**. Supporting both synchronous and asynchronous (Tokio-backed) resolution, this crate provides transport-level encryption and cryptographic authentication for all your DNS queries.

No C bindings, no OpenSSL dependencies, and no unsafe code.

## Features

- **Pure Rust Cryptography**: Built entirely on high-assurance cryptographic implementations (`chacha20`, `poly1305`, `x25519-dalek`, `ed25519-dalek`).
- **Memory Security**: Integrates the `zeroize` crate to securely wipe sensitive keying material and decrypted buffers from memory on drop.
- **MITM Resistance**: Relies on pinned resolver public keys hardcoded at compile-time to prevent CA-level compromise/spoofing.
- **Robust TCP Fallback**: Automatically and transparently retries queries over TCP if the decrypted DNSCrypt response indicates truncation (TC bit set).
- **Consensus Voting (`max-security` mode)**: Queries multiple DNSCrypt providers in parallel and returns only consensus-voted results, discarding minority/unconfirmed IP addresses.
- **Reqwest Integration**: Ships a drop-in `DnscryptResolver` to route all host lookups in a `reqwest::Client` through DNSCrypt.
- **Async & Sync Support**: Complete async/sync dual API.

## Feature Flags

| Feature | Description | Default |
|---------|-------------|---------|
| `tokio` | Enables asynchronous API, async session establishment, and async DNS querying using Tokio. | Yes |
| `zeroize` | Wraps all sensitive state, keys, nonces, and decrypted payloads in zero-on-drop containers. | Yes |
| `reqwest` | Enables the `DnscryptResolver` for integration with `reqwest::Client`. | Yes |
| `max-security` | Aggregates and votes on results from multiple providers, returning only majority-voted answers. | No |

## Cryptographic Specification

| Primitive | Protocol Role | Crate |
|-----------|---------------|-------|
| **X25519 DH** | Per-session key agreement with the resolver | `x25519-dalek` |
| **HChaCha20** | Key derivation from the raw Diffie-Hellman secret | `chacha20` |
| **XChaCha20-Poly1305** | Authenticated encryption of queries/responses | `chacha20` / `poly1305` |
| **Ed25519** | Resolver certificate signature verification | `ed25519-dalek` |

### 1. Simple Synchronous Resolution

```rust
use dnscrypt::{resolve, HARDCODED_RESOLVERS, Error};

fn main() -> Result<(), Error> {
    // Resolve github.com using default built-in resolvers
    let ips = resolve(HARDCODED_RESOLVERS, "github.com")?;
    println!("github.com => {:?}", ips);
    Ok(())
}
```

### 2. Asynchronous (Tokio) Resolution

Make sure the `tokio` feature flag is enabled:

```rust
use dnscrypt::{resolve_async, HARDCODED_RESOLVERS, Error};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let ips = resolve_async(HARDCODED_RESOLVERS, "github.com").await?;
    println!("github.com => {:?}", ips);
    Ok(())
}
```

### 3. Reqwest Client Integration

Enable the `reqwest` feature flag to use `DnscryptResolver`:

```rust
use dnscrypt::DnscryptResolver;
use std::sync::Arc;

#[tokio::main]
async fn main() {
    let resolver = Arc::new(DnscryptResolver::new());
    
    let client = reqwest::Client::builder()
        .dns_resolver(resolver)
        .build()
        .unwrap();

    let res = client.get("https://github.com")
        .send()
        .await
        .unwrap();

    println!("Status: {}", res.status());
}
```

### 4. Custom DNSCrypt Resolvers

Bypass the default list of resolvers by supplying your own custom configurations:

```rust
use dnscrypt::{resolve, HardcodedResolver};
use std::net::{SocketAddr, IpAddr, Ipv4Addr};

const MY_RESOLVERS: &[HardcodedResolver] = &[
    HardcodedResolver {
        ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(9, 9, 9, 9)), 8443),
        provider_name: "2.dnscrypt-cert.quad9.net",
        provider_pk: [
            // Insert your resolver's Ed25519 public key bytes here
            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
            0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
            0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
        ],
    }
];

fn main() {
    let ips = resolve(MY_RESOLVERS, "github.com").unwrap();
    println!("Resolved: {:?}", ips);
}
```

## Security Hardening

This library has undergone systematic hardening to defend against transport-level threats:
- **Zeroization**: Sensitive memory containers (keys, nonces, plaintext packets) are systematically zero-filled on drop when the `zeroize` feature is active.
- **Randomized Padding & TXIDs**: Queries are padded to randomized boundaries to prevent side-channel traffic analysis. Transaction IDs are derived from a cryptographically secure random source (`getrandom`).
- **Encrypted TC Detection**: The parser decrypts responses before validating truncation. If a response is truncated inside the secure envelope, the resolver automatically executes a retry over TCP.
- **Majority Voting**: When configured with `max-security`, minority/non-consensus IP responses are automatically discarded to prevent MITM attacks from compromised or corrupted nodes.

## License

Licensed under either of:
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)

at your option.
