Web Analytics

acme-client

⭐ 2 stars Dutch by anhao

🌐 Taal

ACME Client

Een uitgebreide PHP ACME v2 clientbibliotheek voor het automatiseren van SSL/TLS certificaatbeheer met Let's Encrypt, ZeroSSL en andere ACME-compatibele certificaatautoriteiten.

github statistieken Licentie: MIT PHP Versie

Taal / 语言: Engels | 中文

Functies

Vereisten

Installatie

Installeren via Composer:

composer require alapi/acme-client

Snelstart

1. Lokale Account Sleutels Aanmaken

U heeft twee manieren om ACME-account sleutels aan te maken en te beheren:

Optie A: Bestaande sleutels gebruiken met de Account klasse

use ALAPI\Acme\Accounts\Account;

// Create account from existing private key string $privateKeyPem = '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----'; $account = new Account($privateKeyPem);

// Or create account with both private and public keys $publicKeyPem = '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----'; $account = new Account($privateKeyPem, $publicKeyPem);

// Or create account from private key only (public key will be extracted) $account = Account::fromPrivateKey($privateKeyPem);

Optie B: Gebruik van AccountStorage voor bestandsgebaseerd sleutelbeheer

use ALAPI\Acme\Utils\AccountStorage;

// Create new ECC account and save to files (recommended) $account = AccountStorage::createAndSave( directory: 'storage', name: 'my-account', keyType: 'ECC', keySize: 'P-384' );

// Or create RSA account and save to files $rsaAccount = AccountStorage::createAndSave( directory: 'storage', name: 'my-rsa-account', keyType: 'RSA', keySize: 4096 );

echo "Account keys created and saved successfully!\n";

2. Initialiseer ACME-client

use ALAPI\Acme\AcmeClient; use ALAPI\Acme\Accounts\Account; use ALAPI\Acme\Utils\AccountStorage; use ALAPI\Acme\Http\Clients\ClientFactory;

// Option A: Load account from files $account = AccountStorage::loadFromFiles('storage', 'my-account');

// Option B: Create account from existing keys $privateKey = '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----'; $account = new Account($privateKey);

// Create HTTP client with optional proxy $httpClient = ClientFactory::create(timeout: 30, options: [ // 'proxy' => 'http://proxy.example.com:8080' ]);

// Initialize client for Let's Encrypt production $acmeClient = new AcmeClient( staging: false, // Set to true for testing localAccount: $account, httpClient: $httpClient );

// Or use ZeroSSL $zeroSslClient = new AcmeClient( localAccount: $account, httpClient: $httpClient, baseUrl: 'https://acme.zerossl.com/v2/DV90/directory' );

3. ACME-account registreren

Voor Let's Encrypt (geen EAB vereist):

try {
    // Register account with Let's Encrypt
    $accountData = $acmeClient->account()->create(
        contacts: ['mailto:admin@example.com']
    );
    
    echo "Account registered successfully!\n";
    echo "Account URL: " . $accountData->url . "\n";
} catch (Exception $e) {
    echo "Registration failed: " . $e->getMessage() . "\n";
}

Voor ZeroSSL (EAB vereist):

try {
    // Get EAB credentials from ZeroSSL dashboard
    $eabKid = 'your-eab-kid';
    $eabHmacKey = 'your-eab-hmac-key';
    
    $accountData = $zeroSslClient->account()->create(
        eabKid: $eabKid,
        eabHmacKey: $eabHmacKey,
        contacts: ['mailto:admin@example.com']
    );
    
    echo "ZeroSSL account registered successfully!\n";
} catch (Exception $e) {
    echo "Registration failed: " . $e->getMessage() . "\n";
}

4. Certificaat aanvragen

try { // Get account data $accountData = $acmeClient->account()->get(); // Create new order for domains $domains = ['example.com', 'www.example.com']; $order = $acmeClient->order()->new($accountData, $domains); echo "Order created: " . $order->url . "\n"; echo "Status: " . $order->status . "\n"; // Check domain validations $validations = $acmeClient->domainValidation()->status($order); foreach ($validations as $validation) { $domain = $validation->identifier['value']; echo "Domain: $domain - Status: " . $validation->status . "\n"; if ($validation->isPending()) { // Get validation data for HTTP-01 challenge $challenges = $acmeClient->domainValidation()->getValidationData( [$validation], AuthorizationChallengeEnum::HTTP ); foreach ($challenges as $challenge) { echo "HTTP Challenge for $domain:\n"; echo " File: " . $challenge['filename'] . "\n"; echo " Content: " . $challenge['content'] . "\n"; echo " Place it at: http://$domain/.well-known/acme-challenge/" . $challenge['filename'] . "\n\n"; } } } } catch (Exception $e) { echo "Error: " . $e->getMessage() . "\n"; }

5. Voltooien van Domeinvalidatie

Plaats na het uploaden van de challenge-bestanden op uw webserver:

try {
    // Trigger validation for each domain
    foreach ($validations as $validation) {
        if ($validation->isPending()) {
            $response = $acmeClient->domainValidation()->validate(
                $accountData,
                $validation,
                AuthorizationChallengeEnum::HTTP,
                localTest: true // Performs local validation first
            );
            
            echo "Validation triggered for: " . $validation->identifier['value'] . "\n";
        }
    }
    
    // Wait for validation to complete
    $maxAttempts = 10;
    $attempt = 0;
    
    do {
        sleep(5);
        $attempt++;
        
        // Check order status
        $currentOrder = $acmeClient->order()->get($accountData, $order->url);
        echo "Order status: " . $currentOrder->status . "\n";
        
        if ($currentOrder->status === 'ready') {
            echo "All validations completed successfully!\n";
            break;
        }
        
        if ($currentOrder->status === 'invalid') {
            echo "Order validation failed!\n";
            break;
        }
        
    } while ($attempt < $maxAttempts);
    
} catch (Exception $e) {
    echo "Validation error: " . $e->getMessage() . "\n";
}

6. Genereer en Dien CSR in

use ALAPI\Acme\Security\Cryptography\OpenSsl;

try { if ($currentOrder->status === 'ready') { // Generate Certificate private key $certificatePrivateKey = OpenSsl::generatePrivateKey('RSA', 2048); // Generate Certificate Signing Request (CSR) using OpenSsl helper $csrString = OpenSsl::generateCsr($domains, $certificatePrivateKey); // Export private key for saving $privateKeyString = OpenSsl::openSslKeyToString($certificatePrivateKey); // Submit CSR to finalize order $finalizedOrder = $acmeClient->order()->finalize( $accountData, $currentOrder, $csrString ); echo "Order finalized successfully!\n"; echo "Certificate URL: " . $finalizedOrder->certificateUrl . "\n"; // Download certificate bundle $certificateBundle = $acmeClient->certificate()->get( $accountData, $finalizedOrder->certificateUrl ); // Save certificate and private key file_put_contents('certificate.pem', $certificateBundle->certificate); file_put_contents('fullchain.pem', $certificateBundle->fullchain); file_put_contents('private-key.pem', $privateKeyString); echo "Certificate saved to certificate.pem\n"; echo "Fullchain certificate saved to fullchain.pem\n"; echo "Private key saved to private-key.pem\n"; } } catch (Exception $e) { echo "Certificate generation error: " . $e->getMessage() . "\n"; }

Geavanceerd gebruik

DNS-01 Uitdaging

Voor wildcard certificaten of wanneer HTTP-validatie niet mogelijk is:

// Get DNS challenge data
$dnsChallenge = $acmeClient->domainValidation()->getValidationData(
    [$validation],
    AuthorizationChallengeEnum::DNS
);

foreach ($dnsChallenge as $challenge) { echo "DNS Challenge for " . $challenge['domain'] . ":\n"; echo " Record Name: " . $challenge['domain'] . "\n"; echo " Record Type: TXT\n"; echo " Record Value: " . $challenge['digest'] . "\n\n"; }

// After adding DNS records, trigger validation $response = $acmeClient->domainValidation()->validate( $accountData, $validation, AuthorizationChallengeEnum::DNS, localTest: true );

Certificaatvernieuwing met ARI

use ALAPI\Acme\Management\RenewalManager;

// Load existing certificate $certificatePem = file_get_contents('certificate.pem');

// Create renewal manager $renewalManager = $acmeClient->renewalManager(defaultRenewalDays: 30);

// Check if renewal is needed if ($renewalManager->shouldRenew($certificatePem)) { echo "Certificate needs renewal\n"; // Get ARI information if supported if ($acmeClient->directory()->supportsARI()) { $renewalInfo = $acmeClient->renewalInfo()->getFromCertificate($certificatePem); echo "Suggested renewal window:\n"; echo " Start: " . $renewalInfo->suggestedWindow['start'] . "\n"; echo " End: " . $renewalInfo->suggestedWindow['end'] . "\n"; if ($renewalInfo->shouldRenewNow()) { echo "ARI recommends renewing now\n"; // Proceed with renewal... } } } else { echo "Certificate renewal not needed yet\n"; }

Certificaatintrekking

try {
    // Load certificate to revoke
    $certificatePem = file_get_contents('certificate.pem');
    
    // Revoke certificate
    $success = $acmeClient->certificate()->revoke(
        $certificatePem,
        reason: 1 // 0=unspecified, 1=keyCompromise, 2=cACompromise, 3=affiliationChanged, 4=superseded, 5=cessationOfOperation
    );
    
    if ($success) {
        echo "Certificate revoked successfully\n";
    } else {
        echo "Certificate revocation failed\n";
    }
    
} catch (Exception $e) {
    echo "Revocation error: " . $e->getMessage() . "\n";
}

Meerdere certificaatautoriteiten

// Let's Encrypt
$letsEncrypt = new AcmeClient(
    staging: false,
    localAccount: $account,
    httpClient: $httpClient
);

// ZeroSSL $zeroSSL = new AcmeClient( localAccount: $account, httpClient: $httpClient, baseUrl: 'https://acme.zerossl.com/v2/DV90/directory' );

// Google Trust Services $googleCA = new AcmeClient( localAccount: $account, httpClient: $httpClient, baseUrl: 'https://dv.acme-v02.api.pki.goog/directory' );

Aangepaste HTTP-clientconfiguratie

use ALAPI\Acme\Http\Clients\ClientFactory;

$httpClient = ClientFactory::create(30, [ 'proxy' => 'http://proxy.example.com:8080', 'verify' => true, // SSL verification 'timeout' => 30, 'connect_timeout' => 10, 'headers' => [ 'User-Agent' => 'MyApp ACME Client 1.0' ] ]);

Logging

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Create logger $logger = new Logger('acme'); $logger->pushHandler(new StreamHandler('acme.log', Logger::INFO));

// Set logger on client $acmeClient->setLogger($logger);

Configuratie

Opties voor Accountbeheer

Gebruik van AccountStorage voor bestand-gebaseerd beheer:

use ALAPI\Acme\Utils\AccountStorage;

// Check if account files exist if (AccountStorage::exists('storage', 'my-account')) { $account = AccountStorage::loadFromFiles('storage', 'my-account'); } else { $account = AccountStorage::createAndSave('storage', 'my-account'); }

// Load or create account automatically $account = AccountStorage::loadOrCreate( directory: 'storage', name: 'my-account', keyType: 'ECC', keySize: 'P-384' );

Gebruik van de Account-klasse voor bestaande sleutels:

use ALAPI\Acme\Accounts\Account;

// From existing private key $privateKey = file_get_contents('/path/to/private.key'); $account = new Account($privateKey);

// With both private and public keys $privateKey = file_get_contents('/path/to/private.key'); $publicKey = file_get_contents('/path/to/public.key'); $account = new Account($privateKey, $publicKey);

// Create new account with specific key type $account = Account::createECC('P-384'); // or 'P-256', 'P-384' $account = Account::createRSA(4096); // or 2048, 3072

// Get account information echo "Key Type: " . $account->getKeyType() . "\n"; echo "Key Size: " . $account->getKeySize() . "\n";

Foutafhandeling

use ALAPI\Acme\Exceptions\AcmeException;
use ALAPI\Acme\Exceptions\AcmeAccountException;
use ALAPI\Acme\Exceptions\DomainValidationException;
use ALAPI\Acme\Exceptions\AcmeCertificateException;

try { // ACME operations here } catch (AcmeAccountException $e) { echo "Account error: " . $e->getMessage() . "\n"; echo "Detail: " . $e->getDetail() . "\n"; echo "Type: " . $e->getAcmeType() . "\n"; } catch (DomainValidationException $e) { echo "Validation error: " . $e->getMessage() . "\n"; } catch (AcmeCertificateException $e) { echo "Certificate error: " . $e->getMessage() . "\n"; } catch (AcmeException $e) { echo "ACME error: " . $e->getMessage() . "\n"; } catch (Exception $e) { echo "General error: " . $e->getMessage() . "\n"; }

Testen

Voer de testreeks uit:

composer test

Voer statische analyse uit:

composer analyse

Codeerstijl corrigeren:

composer cs-fix

Veiligheidsoverwegingen

Bijdragen

Licentie

Dit project is gelicentieerd onder de MIT-licentie – zie het LICENSE bestand voor details.

Links

Ondersteuning

Als je problemen tegenkomt of vragen hebt:

--- Tranlated By Open Ai Tx | Last indexed: 2025-08-15 ---