This document describes the ACME (Automated Certificate Management Environment) protocol support in TOSSL, which enables automated SSL/TLS certificate issuance and management using Let's Encrypt and other ACME-compliant certificate authorities.
ACME is the protocol used by Let's Encrypt and other certificate authorities to automate the process of certificate issuance, validation, and renewal. TOSSL provides a complete ACME v2 implementation with support for:
- ACME v2 protocol - Full RFC 8555 compliance
- DNS-01 challenges - Domain validation via DNS TXT records
- HTTP-01 challenges - Domain validation via HTTP (planned)
- Multiple DNS providers - Cloudflare, Route53, generic DNS APIs
- Certificate lifecycle management - Issuance, renewal, revocation
- TOSSL library - Built with ACME support enabled
- libcurl - HTTP client library for ACME communication
- json-c - JSON parsing library
- OpenSSL - Cryptographic operations
- Network access - To ACME servers and DNS APIs
# Ubuntu/Debian
sudo apt-get install libcurl4-openssl-dev libjson-c-dev
# CentOS/RHEL
sudo yum install libcurl-devel json-c-devel
# macOS
brew install curl json-c# Build TOSSL with ACME support
make clean && make
# Verify ACME commands are available
echo 'load ./libtossl.so; puts [info commands tossl::acme::*]' | tclshExpected output:
::tossl::acme::cleanup_dns ::tossl::acme::dns01_challenge ::tossl::acme::create_account ::tossl::acme::directory ::tossl::acme::create_order
Fetches and parses the ACME directory from the specified URL.
Parameters:
directory_url- ACME server directory URL (e.g., Let's Encrypt staging)
Returns: Tcl dict containing ACME endpoints
Example:
set directory [tossl::acme::directory "https://acme-staging-v02.api.letsencrypt.org/directory"]
puts "New account URL: [dict get $directory newAccount]"
puts "New order URL: [dict get $directory newOrder]"Creates a new ACME account with the specified email address.
Parameters:
directory_url- ACME server directory URLaccount_key- PEM-encoded private key for accountemail- Email address for accountcontact- Additional contact information (optional)
Returns: Account creation status
Example:
set account_key [tossl::key::generate -type rsa -bits 2048]
set result [tossl::acme::create_account \
"https://acme-staging-v02.api.letsencrypt.org/directory" \
[dict get $account_key private] \
"admin@example.com"]Creates a new certificate order for the specified domains.
Parameters:
directory_url- ACME server directory URLaccount_key- PEM-encoded private key for accountdomains- Space-separated list of domain names
Returns: Order creation status
Example:
set result [tossl::acme::create_order \
"https://acme-staging-v02.api.letsencrypt.org/directory" \
$account_key \
"example.com www.example.com"]Prepares a DNS-01 challenge by creating the required DNS TXT record.
Parameters:
domain- Domain name for certificatetoken- ACME challenge tokenaccount_key- PEM-encoded private key for accountprovider- DNS provider ("cloudflare", "route53", "generic")api_key- DNS provider API keyzone_id- DNS zone ID (required for Cloudflare)
Returns: Challenge information dict
Example:
set challenge [tossl::acme::dns01_challenge \
"example.com" \
"challenge-token-12345" \
$account_key \
"cloudflare" \
"your-cloudflare-api-key" \
"your-zone-id"]
puts "DNS record name: [dict get $challenge dns_record_name]"
puts "DNS record value: [dict get $challenge dns_record_value]"Removes the DNS TXT record created for the challenge.
Parameters:
domain- Domain namerecord_name- DNS record name to deleteprovider- DNS providerapi_key- DNS provider API keyzone_id- DNS zone ID (required for Cloudflare)
Returns: Cleanup status
Example:
set result [tossl::acme::cleanup_dns \
"example.com" \
"_acme-challenge.example.com" \
"cloudflare" \
"your-cloudflare-api-key" \
"your-zone-id"]Setup:
- Get API key from Cloudflare dashboard
- Get zone ID for your domain
- Ensure domain is managed by Cloudflare
Usage:
set provider "cloudflare"
set api_key "your-cloudflare-api-key"
set zone_id "your-zone-id"Setup:
- Configure AWS credentials
- Ensure domain is managed by Route53
- Set up IAM permissions for DNS management
Usage:
set provider "route53"
set api_key "your-aws-access-key"
set api_secret "your-aws-secret-key"Setup:
- Configure DNS API endpoint
- Set up authentication credentials
- Ensure API supports TXT record management
Usage:
set provider "generic"
set api_key "your-api-key"
set endpoint "https://your-dns-api.com"Here's a complete example of issuing a certificate using DNS-01 challenge:
#!/usr/bin/env tclsh
# Load TOSSL
if {[catch {package require tossl}]} {
load ./libtossl.so
}
# Configuration
set acme_server "https://acme-staging-v02.api.letsencrypt.org/directory"
set domain "example.com"
set email "admin@example.com"
set dns_provider "cloudflare"
set dns_api_key "your-cloudflare-api-key"
set dns_zone_id "your-zone-id"
# Step 1: Generate account key
puts "Generating account key..."
set account_keys [tossl::key::generate -type rsa -bits 2048]
set account_private [dict get $account_keys private]
# Step 2: Create ACME account
puts "Creating ACME account..."
set account_result [tossl::acme::create_account $acme_server $account_private $email]
puts "Account creation: $account_result"
# Step 3: Create certificate order
puts "Creating certificate order..."
set order_result [tossl::acme::create_order $acme_server $account_private $domain]
puts "Order creation: $order_result"
# Step 4: Prepare DNS-01 challenge
puts "Preparing DNS-01 challenge..."
set token "challenge-token-12345"
set challenge [tossl::acme::dns01_challenge \
$domain $token $account_private $dns_provider $dns_api_key $dns_zone_id]
puts "DNS record name: [dict get $challenge dns_record_name]"
puts "DNS record value: [dict get $challenge dns_record_value]"
# Step 5: Wait for DNS propagation (in real usage)
puts "Waiting for DNS propagation..."
after 30000 ; # Wait 30 seconds
# Step 6: Clean up DNS record
puts "Cleaning up DNS record..."
set cleanup_result [tossl::acme::cleanup_dns \
$domain \
[dict get $challenge dns_record_name] \
$dns_provider \
$dns_api_key \
$dns_zone_id]
puts "Cleanup: $cleanup_result"
puts "Certificate issuance process completed!"The staging server is perfect for testing:
# Test directory fetch
set directory [tossl::acme::directory "https://acme-staging-v02.api.letsencrypt.org/directory"]
puts "Available endpoints: [dict keys $directory]"
# Test account creation
set account_key [tossl::key::generate -type rsa -bits 2048]
set result [tossl::acme::create_account \
"https://acme-staging-v02.api.letsencrypt.org/directory" \
[dict get $account_key private] \
"test@example.com"]
puts "Account creation result: $result"# Run the complete ACME integration test
tclsh test_acme_integration.tclExpected output:
Testing ACME functionality with DNS-01 challenge support...
========================================================
1. Testing HTTP functionality...
Status: 200
✓ HTTP functionality working
2. Testing ACME directory fetch...
Directory keys: keyChange meta newAccount newNonce newOrder renewalInfo revokeCert
✓ ACME directory fetch working
3. Testing account key generation...
✓ Account key generated
4. Testing ACME account creation...
Result: Account created successfully
✓ ACME account creation working
5. Testing ACME order creation...
Result: Order created successfully
✓ ACME order creation working
6. Testing DNS-01 challenge preparation...
Challenge type: dns-01
DNS record name: _acme-challenge.example.com
DNS record value: placeholder-dns01-value
✓ DNS-01 challenge preparation working
7. Testing DNS cleanup...
Result: DNS record deleted successfully
✓ DNS cleanup working
For production certificates, use the production server:
set production_server "https://acme-v02.api.letsencrypt.org/directory"Let's Encrypt has rate limits:
- Staging: No limits (for testing)
- Production:
- 50 certificates per registered domain per week
- 300 new orders per account per 3 hours
- 5 duplicate orders per account per week
For automatic renewal, implement a cron job or systemd timer:
#!/bin/bash
# Renew certificates every 60 days
tclsh /path/to/renewal_script.tcl- Check network connectivity to ACME server
- Verify libcurl and json-c are properly installed
- Ensure HTTP response is valid JSON
- Verify DNS provider credentials
- Check zone ID for Cloudflare
- Ensure domain is managed by the DNS provider
- Verify API permissions
- Increase wait time for DNS propagation
- Check DNS provider's propagation time
- Verify DNS record was created correctly
Enable debug output by setting environment variables:
export TOSSL_DEBUG=1
export TOSSL_ACME_DEBUG=1
tclsh your_acme_script.tclCheck system logs for detailed error information:
# Check for TOSSL-related errors
journalctl -f | grep tossl
# Check DNS provider API logs
tail -f /var/log/dns-provider.log- Store account private keys securely
- Use appropriate file permissions (600)
- Consider hardware security modules (HSM) for production
- Rotate keys periodically
- Use environment variables for API keys
- Implement least-privilege access for DNS APIs
- Monitor API usage for unauthorized access
- Rotate API keys regularly
- Validate certificate contents before deployment
- Monitor certificate expiration
- Implement certificate transparency logging
- Use appropriate key sizes (RSA 2048+ or ECDSA P-256+)
ACME HTTP responses return a dict with:
status_code- HTTP status codebody- Response body (JSON)headers- Response headers
ACME directory responses contain:
newAccount- Account registration endpointnewOrder- Order creation endpointnewNonce- Nonce generation endpointkeyChange- Account key change endpointrevokeCert- Certificate revocation endpoint
All ACME commands return TCL_OK on success or TCL_ERROR on failure. Check the interp result for error details:
if {[catch {
set result [tossl::acme::create_account $url $key $email]
} err]} {
puts "Error: $err"
return
}To contribute to ACME support:
- Report bugs - Create issues with detailed error information
- Add DNS providers - Implement new DNS provider integrations
- Improve error handling - Add better error messages and recovery
- Add tests - Create comprehensive test suites
- Documentation - Improve this README and add examples
TOSSL ACME support is licensed under the Apache License 2.0. See LICENSE file for details.
For support with ACME functionality:
- Check this README for common solutions
- Review the test files for usage examples
- Check the TOSSL main documentation
- Create an issue with detailed error information
Note: This ACME implementation is designed for educational and development purposes. For production use, consider using established ACME clients like certbot or acme.sh for critical systems.