acme2-eab 0.5.7

A Tokio and OpenSSL based ACMEv2 client.
Documentation
use crate::common::chaltestsrv::TestServ;
use crate::common::pebble::PebbleBuilder;
use crate::common::test_env::TestEnv;
use acme2_eab::*;
use common::pebble::Pebble;
use data_encoding::BASE64URL_NOPAD;
use openssl::pkey::PKey;
use serde_json::json;
use std::sync::Arc;
use std::time::Duration;

mod common;

async fn pebble_http_client() -> reqwest::Client {
    let raw = tokio::fs::read("./certs/pebble.minica.pem").await.unwrap();
    let cert = reqwest::Certificate::from_pem(&raw).unwrap();
    reqwest::Client::builder()
        .add_root_certificate(cert)
        .build()
        .unwrap()
}

async fn pebble_directory(pebble: &Pebble) -> Arc<Directory> {
    let http_client = pebble_http_client().await;

    DirectoryBuilder::new(pebble.directory_url.to_string())
        .http_client(http_client)
        .build()
        .await
        .unwrap()
}

async fn pebble_account(pebble: &Pebble) -> Arc<Account> {
    let dir = pebble_directory(pebble).await;
    let mut builder = AccountBuilder::new(dir);

    let account = builder
        .contact(vec!["mailto:hello@lcas.dev".to_string()])
        .terms_of_service_agreed(true)
        .build()
        .await
        .unwrap();

    assert!(!account.id.is_empty());

    account
}

#[tokio::test]
async fn test_client_creation_pebble() {
    let mut env = TestEnv::new("test_client_creation_pebble");
    let pebble = Pebble::new_default(&mut env).await.unwrap();

    let dir = pebble_directory(&pebble).await;

    let meta = dir.meta.clone().unwrap();

    assert_eq!(meta.caa_identities, None);
    assert_eq!(meta.website, None);
    env.stop().await;
}

#[tokio::test]
async fn test_account_creation_pebble() {
    let mut env = TestEnv::new("test_account_creation_pebble");
    let pebble = Pebble::new_default(&mut env).await.unwrap();

    let dir = pebble_directory(&pebble).await;

    let mut builder = AccountBuilder::new(dir.clone());
    let account = builder
        .contact(vec!["mailto:hello@lcas.dev".to_string()])
        .terms_of_service_agreed(true)
        .build()
        .await
        .unwrap();

    assert!(!account.id.is_empty());

    let mut builder = AccountBuilder::new(dir.clone());
    let account2 = builder
        .contact(vec!["mailto:hello@lcas.dev".to_string()])
        .terms_of_service_agreed(true)
        .build()
        .await
        .unwrap();

    assert!(!account.id.is_empty());
    assert_eq!(account.status, AccountStatus::Valid);
    assert_eq!(account2.status, AccountStatus::Valid);
    env.stop().await;
}

#[tokio::test]
async fn test_account_creation_pebble_eab() {
    let private_key = "zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W";
    let key_id = "kid-1";

    let mut env = TestEnv::new("test_account_creation_pebble_eab");
    let pebble = PebbleBuilder::new()
        .with_eab_keypair(key_id, private_key)
        .build(&mut env)
        .await
        .unwrap();

    let dir = pebble_directory(&pebble).await;

    let eab_key = {
        let value_b64 = private_key;
        let value = BASE64URL_NOPAD.decode(value_b64.as_bytes()).unwrap();
        PKey::hmac(&value).unwrap()
    };

    let mut builder = AccountBuilder::new(dir.clone());
    let account = builder
        .contact(vec!["mailto:hello@lcas.dev".to_string()])
        .external_account_binding(key_id.to_string(), eab_key)
        .terms_of_service_agreed(true)
        .build()
        .await
        .unwrap();

    assert!(!account.id.is_empty());
    assert_eq!(account.status, AccountStatus::Valid);

    env.stop().await;
}

#[tokio::test]
async fn test_order_http01_challenge_pebble_rsa() {
    let mut env = TestEnv::new("test_order_http01_challenge_pebble_rsa");
    let testserv = TestServ::new(&mut env).await.unwrap();
    let pebble = PebbleBuilder::new()
        .with_http_port(testserv.http_port)
        .build(&mut env)
        .await
        .unwrap();

    let account = pebble_account(&pebble).await;

    let mut builder = OrderBuilder::new(account);
    let order = builder
        .add_dns_identifier("host.docker.internal".to_string())
        .build()
        .await
        .unwrap();

    let authorizations = order.authorizations().await.unwrap();

    let client = reqwest::Client::new();
    for auth in authorizations {
        let challenge = auth.get_challenge("http-01").unwrap();

        assert_eq!(challenge.status, ChallengeStatus::Pending);

        let response = client
            .post(format!("{}add-http01", testserv.management_url))
            .json(&json!({
              "token": challenge.token,
              "content": challenge.key_authorization().unwrap().unwrap()
            }))
            .send()
            .await
            .unwrap();

        assert!(response.status().is_success());

        let challenge = challenge.validate().await.unwrap();
        let challenge = challenge
            .wait_done(Duration::from_secs(5), 3)
            .await
            .unwrap();

        println!("{:#?}", challenge.error);
        assert_eq!(challenge.status, ChallengeStatus::Valid);

        let authorization = auth.wait_done(Duration::from_secs(5), 3).await.unwrap();
        assert_eq!(authorization.status, AuthorizationStatus::Valid);
    }

    assert_eq!(order.status, OrderStatus::Pending);

    let order = order.wait_ready(Duration::from_secs(5), 3).await.unwrap();

    assert_eq!(order.status, OrderStatus::Ready);

    let pkey = gen_rsa_private_key(4096).unwrap();
    let order = order.finalize(Csr::Automatic(pkey)).await.unwrap();

    let order = order.wait_done(Duration::from_secs(5), 3).await.unwrap();

    assert_eq!(order.status, OrderStatus::Valid);

    let cert = order.certificate().await.unwrap().unwrap();
    assert!(cert.len() > 1);

    env.stop().await;
}

#[tokio::test]
async fn test_order_http01_challenge_pebble_ec() {
    let mut env = TestEnv::new("test_order_http01_challenge_pebble_ec");
    let testserv = TestServ::new(&mut env).await.unwrap();
    let pebble = PebbleBuilder::new()
        .with_http_port(testserv.http_port)
        .build(&mut env)
        .await
        .unwrap();

    let account = pebble_account(&pebble).await;

    let mut builder = OrderBuilder::new(account);
    let order = builder
        .add_dns_identifier("host.docker.internal".to_string())
        .build()
        .await
        .unwrap();

    let authorizations = order.authorizations().await.unwrap();

    let client = pebble_http_client().await;
    for auth in authorizations {
        let challenge = auth.get_challenge("http-01").unwrap();

        assert_eq!(challenge.status, ChallengeStatus::Pending);

        client
            .post(format!("{}add-http01", testserv.management_url))
            .json(&json!({
              "token": challenge.token,
              "content": challenge.key_authorization().unwrap().unwrap()
            }))
            .send()
            .await
            .unwrap();

        let challenge = challenge.validate().await.unwrap();
        let challenge = challenge
            .wait_done(Duration::from_secs(5), 3)
            .await
            .unwrap();

        println!("{:#?}", challenge.error);
        assert_eq!(challenge.status, ChallengeStatus::Valid);

        let authorization = auth.wait_done(Duration::from_secs(5), 3).await.unwrap();
        assert_eq!(authorization.status, AuthorizationStatus::Valid)
    }

    assert_eq!(order.status, OrderStatus::Pending);

    let order = order.wait_ready(Duration::from_secs(5), 3).await.unwrap();

    assert_eq!(order.status, OrderStatus::Ready);

    let pkey = gen_ec_p256_private_key().unwrap();
    let order = order.finalize(Csr::Automatic(pkey)).await.unwrap();

    let order = order.wait_done(Duration::from_secs(5), 3).await.unwrap();

    assert_eq!(order.status, OrderStatus::Valid);

    let cert = order.certificate().await.unwrap().unwrap();
    assert!(cert.len() > 1);

    env.stop().await;
}

#[tokio::test]
async fn test_order_dns01_challenge_pebble() {
    let mut env = TestEnv::new("test_order_dns01_challenge_pebble");
    let testserv = TestServ::new(&mut env).await.unwrap();
    let pebble = PebbleBuilder::new()
        .with_dns_port(testserv.dns_port)
        .build(&mut env)
        .await
        .unwrap();

    let account = pebble_account(&pebble).await;

    let mut builder = OrderBuilder::new(account);
    let order = builder
        .add_dns_identifier("test-order-dns01-challenge-pebble.lcas.dev".to_string())
        .build()
        .await
        .unwrap();
    let authorizations = order.authorizations().await.unwrap();
    let client = pebble_http_client().await;

    for auth in authorizations {
        let challenge = auth.get_challenge("dns-01").unwrap();

        assert_eq!(challenge.status, ChallengeStatus::Pending);
        client
            .post(format!("{}set-txt", testserv.management_url))
            .json(&json!({
              "host": "_acme-challenge.test-order-dns01-challenge-pebble.lcas.dev.",
              "value": challenge.key_authorization_encoded().unwrap().unwrap(),
            }))
            .send()
            .await
            .unwrap();
        let challenge = challenge.validate().await.unwrap();
        let challenge = challenge
            .wait_done(Duration::from_secs(5), 3)
            .await
            .unwrap();
        println!("{:#?}", challenge.error);
        assert_eq!(challenge.status, ChallengeStatus::Valid);

        let authorization = auth.wait_done(Duration::from_secs(5), 3).await.unwrap();
        assert_eq!(authorization.status, AuthorizationStatus::Valid)
    }

    assert_eq!(order.status, OrderStatus::Pending);
    let order = order.wait_ready(Duration::from_secs(5), 3).await.unwrap();
    assert_eq!(order.status, OrderStatus::Ready);
    let pkey = gen_rsa_private_key(4096).unwrap();
    let order = order.finalize(Csr::Automatic(pkey)).await.unwrap();
    let order = order.wait_done(Duration::from_secs(5), 3).await.unwrap();
    assert_eq!(order.status, OrderStatus::Valid);
    let cert = order.certificate().await.unwrap().unwrap();
    assert!(cert.len() > 1);

    env.stop().await;
}