acmex 0.8.0

AcmeX: High-performance, extensible ACME v2 (RFC 8555) client and server in Rust, supporting multiple DNS providers, storage backends, and crypto libraries.
Documentation
# Challenge Solver Integration Examples

## Complete Workflow Example

```rust
use acmex::{
    ChallengeSolver, ChallengeSolverRegistry, Http01Solver, Dns01Solver,
    ChallengeType, AcmeConfig, DirectoryManager, NonceManager, AccountManager, KeyPair,
};
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<()> {
    // 1. Setup ACME client
    let config = AcmeConfig::lets_encrypt_staging()
        .with_contact(Contact::email("admin@example.com"));

    let http_client = reqwest::Client::new();
    let dir_mgr = DirectoryManager::new(&config.directory_url, http_client.clone());
    let directory = dir_mgr.get().await?;

    // 2. Create account
    let key_pair = KeyPair::generate()?;
    let nonce_mgr = NonceManager::new(&directory.new_nonce, http_client.clone());
    let account_mgr = AccountManager::new(
        &key_pair,
        &nonce_mgr,
        &dir_mgr,
        &http_client,
    )?;

    let account = account_mgr.register(
        vec![Contact::email("admin@example.com")],
        true,
    ).await?;

    // 3. Create order for domains
    let order_req = NewOrderRequest::new(vec![
        "example.com".to_string(),
        "www.example.com".to_string(),
    ]);

    // 4. Setup challenge solvers
    let mut registry = ChallengeSolverRegistry::new();
    registry.register(Http01Solver::default_addr());
    registry.register(Dns01Solver::with_mock("example.com".to_string()));

    // 5. For each authorization, solve challenge
    // ... (Order creation and challenge handling)

    Ok(())
}
```

## HTTP-01 Only Example

```rust
use acmex::{Http01Solver, ChallengeSolver};
use std::net::SocketAddr;

#[tokio::main]
async fn main() -> Result<()> {
    let addr: SocketAddr = "0.0.0.0:80".parse()?;
    let mut solver = Http01Solver::new(addr);

    // Get challenge from ACME server...
    let challenge = get_acme_challenge().await?;
    let key_auth = compute_key_authorization(&challenge)?;

    // Prepare: start HTTP server
    solver.prepare(&challenge, &key_auth).await?;
    println!("✅ HTTP server running on port 80");

    // Present: tell ACME server we're ready
    solver.present().await?;
    println!("✅ Presented to ACME server");

    // Wait for ACME validation (ACME server checks our HTTP endpoint)
    tokio::time::sleep(Duration::from_secs(5)).await;

    // Verify: check if we solved it
    match solver.verify().await? {
        true => println!("✅ Challenge verified"),
        false => println!("❌ Challenge not verified"),
    }

    // Cleanup: stop HTTP server
    solver.cleanup().await?;
    println!("✅ Cleaned up");

    Ok(())
}
```

## DNS-01 Only Example

```rust
use acmex::{Dns01Solver, ChallengeSolver, DnsProvider, MockDnsProvider};

#[tokio::main]
async fn main() -> Result<()> {
    // Using mock provider for demo
    let mut solver = Dns01Solver::with_mock("example.com".to_string());

    let challenge = get_acme_challenge().await?;
    let key_auth = compute_key_authorization(&challenge)?;

    // Prepare: create DNS record
    solver.prepare(&challenge, &key_auth).await?;
    println!("✅ DNS TXT record created");
    println!("   _acme-challenge.example.com = {}", key_auth);

    // Present: tell ACME we're ready
    solver.present().await?;
    println!("✅ Presented to ACME server");

    // Wait for DNS propagation
    tokio::time::sleep(Duration::from_secs(10)).await;

    // Verify
    match solver.verify().await? {
        true => println!("✅ Challenge verified"),
        false => println!("❌ Challenge not verified"),
    }

    // Cleanup: delete DNS record
    solver.cleanup().await?;
    println!("✅ DNS record deleted");

    Ok(())
}
```

## Multi-Domain HTTP-01 Challenge

```rust
use acmex::{Http01Solver, ChallengeSolver, NewOrderRequest};

#[tokio::main]
async fn main() -> Result<()> {
    // Setup
    let mut http_solver = Http01Solver::new("0.0.0.0:80".parse()?);

    // Create order for multiple domains
    let order_req = NewOrderRequest::new(vec![
        "example.com".to_string(),
        "www.example.com".to_string(),
        "api.example.com".to_string(),
    ]);

    // Get all authorizations
    let authorizations = create_and_get_authorizations(&order_req).await?;

    // For each authorization, create and present HTTP-01 challenge
    for auth in &authorizations {
        for challenge in &auth.challenges {
            if challenge.challenge_type == "http-01" {
                let key_auth = compute_key_authorization(challenge)?;

                // Both domains can use same HTTP solver (router handles routing)
                http_solver.prepare(challenge, &key_auth).await?;
                http_solver.present().await?;
            }
        }
    }

    // Wait for ACME validation
    wait_for_acme_validation().await?;

    // Cleanup
    http_solver.cleanup().await?;

    // Continue with order finalization...
    Ok(())
}
```

## Custom DNS Provider Example

```rust
use acmex::{Dns01Solver, DnsProvider};
use async_trait::async_trait;
use std::sync::Arc;

/// Your custom DNS provider implementation
struct MyCloudFlareProvider {
    api_token: String,
    zone_id: String,
}

#[async_trait]
impl DnsProvider for MyCloudFlareProvider {
    async fn create_txt_record(&self, domain: &str, value: &str) -> Result<String> {
        // Call CloudFlare API
        let record_id = call_cloudflare_api(
            &self.api_token,
            &self.zone_id,
            "POST",
            &format!("record-name={}&record-type=TXT&value={}", domain, value),
        )
            .await?;
        Ok(record_id)
    }

    async fn delete_txt_record(&self, domain: &str, record_id: &str) -> Result<()> {
        // Call CloudFlare delete API
        call_cloudflare_api(
            &self.api_token,
            &self.zone_id,
            "DELETE",
            &format!("record-id={}", record_id),
        )
            .await?;
        Ok(())
    }

    async fn verify_record(&self, domain: &str, value: &str) -> Result<bool> {
        // Query CloudFlare API to check if record exists
        let exists = query_cloudflare_dns(&self.api_token, domain, value).await?;
        Ok(exists)
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    let provider = Arc::new(MyCloudFlareProvider {
        api_token: "your-api-token".to_string(),
        zone_id: "your-zone-id".to_string(),
    });

    let mut solver = Dns01Solver::new(provider, "example.com".to_string());

    // Use as normal
    let challenge = get_challenge().await?;
    solver.prepare(&challenge, &key_auth).await?;
    solver.present().await?;
    solver.cleanup().await?;

    Ok(())
}
```

## Retry and Error Handling

```rust
use acmex::ChallengeSolver;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<()> {
    let mut solver = Http01Solver::new("0.0.0.0:80".parse()?);

    // Prepare with retries
    for attempt in 0..3 {
        match solver.prepare(&challenge, &key_auth).await {
            Ok(_) => {
                println!("✅ Prepared on attempt {}", attempt + 1);
                break;
            }
            Err(e) if attempt < 2 => {
                eprintln!("⚠️  Prepare failed: {}, retrying...", e);
                tokio::time::sleep(Duration::from_secs(2)).await;
            }
            Err(e) => return Err(e),
        }
    }

    // Present
    solver.present().await?;

    // Verify with timeout
    let verified = tokio::time::timeout(
        Duration::from_secs(60),
        solver.verify()
    )
        .await??;

    if !verified {
        eprintln!("❌ Challenge verification failed");
    }

    // Always cleanup
    if let Err(e) = solver.cleanup().await {
        eprintln!("⚠️  Cleanup failed: {}", e);
    }

    Ok(())
}
```

## Concurrent Challenges

```rust
use futures::future::join_all;

#[tokio::main]
async fn main() -> Result<()> {
    let challenges = get_multiple_challenges().await?;

    // Create solver for each challenge
    let mut solvers = Vec::new();
    for challenge in &challenges {
        let mut solver = create_appropriate_solver(challenge)?;
        solvers.push(solver);
    }

    // Prepare all in parallel
    let prepare_tasks = solvers
        .iter_mut()
        .zip(&challenges)
        .map(|(solver, challenge)| {
            let key_auth = compute_key_authorization(challenge)?;
            solver.prepare(challenge, &key_auth)
        });

    join_all(prepare_tasks).await
        .into_iter()
        .collect::<Result<Vec<_>>>()?;

    println!("✅ All challenges prepared");

    // Present all
    let present_tasks = solvers.iter().map(|s| s.present());
    join_all(present_tasks).await
        .into_iter()
        .collect::<Result<Vec<_>>>()?;

    // Wait for validation
    tokio::time::sleep(Duration::from_secs(5)).await;

    // Cleanup all
    let cleanup_tasks = solvers.iter_mut().map(|s| s.cleanup());
    join_all(cleanup_tasks).await
        .into_iter()
        .collect::<Result<Vec<_>>>()?;

    println!("✅ All challenges completed");
    Ok(())
}
```

---

## Testing Examples

Run tests with:

```bash
cargo test --lib challenge::http01
cargo test --lib challenge::dns01
```

Test-specific usage:

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_http01_workflow() {
        let mut solver = Http01Solver::new("127.0.0.1:9999".parse().unwrap());

        let challenge = Challenge { /* ... */ };
        let key_auth = "test-token.test-thumbprint";

        solver.prepare(&challenge, key_auth).await.unwrap();
        assert!(solver.verify().await.unwrap());
        solver.cleanup().await.unwrap();
    }

    #[tokio::test]
    async fn test_dns01_workflow() {
        let mut solver = Dns01Solver::with_mock("example.com".to_string());

        let challenge = Challenge { /* ... */ };
        let key_auth = "test-token.test-thumbprint";

        solver.prepare(&challenge, key_auth).await.unwrap();
        assert!(solver.verify().await.unwrap());
        solver.cleanup().await.unwrap();
    }
}
```

---

**Version**: v0.2.0  
**Last Updated**: 2026-02-07