You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+56-56Lines changed: 56 additions & 56 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,7 +11,7 @@
11
11
12
12
**A PHP 8.3+ ACME v2 client for issuing, renewing, and revoking TLS certificates.** Works with Let's Encrypt, ZeroSSL, Google Trust Services, SSL.com, Buypass, and any RFC 8555-compliant CA. Fluent API, no framework dependencies, and solid test coverage.
13
13
14
-
ACME (Automatic Certificate Management Environment) is the protocol behind free, automated TLS certificates. CoyoteCert covers the full thing: account management, order lifecycle, HTTP-01, DNS-01, and TLS-ALPN-01 challenges, certificate issuance, smart renewal with ARI, and revocation. One `composer require` and you're set.
14
+
ACME (Automatic Certificate Management Environment) is the protocol behind free, automated TLS certificates. Yes, same name as the cartoon supply company. CoyoteCert covers the full thing: account management, order lifecycle, HTTP-01, DNS-01, and TLS-ALPN-01 challenges, certificate issuance, smart renewal with ARI, and revocation. One `composer require` and you're set.
15
15
16
16
---
17
17
@@ -51,23 +51,23 @@ Filesystem with file locking, PDO (MySQL, PostgreSQL, SQLite) with dialect-aware
51
51
52
52
### Typed exceptions for every failure mode
53
53
54
-
`RateLimitException` carries the CA's `Retry-After` seconds so your retry logic is precise. `AuthException` tells you credentials failed — not a transient error. `AcmeException::getSubproblems()` surfaces RFC 8555 §6.7 per-identifier errors so a multi-domain order can report exactly which domains were rejected and why. All exceptions share a common base so a single `catch` still works when you don't need the detail.
54
+
`RateLimitException` carries the CA's `Retry-After` seconds so your retry logic is precise. `AuthException` tells you credentials failed, not a transient error. `AcmeException::getSubproblems()` surfaces RFC 8555 §6.7 per-identifier errors so a multi-domain order can report exactly which domains were rejected and why. All exceptions share a common base so a single `catch` still works when you don't need the detail.
55
55
56
56
### CAA pre-check
57
57
58
-
Before submitting an order, CoyoteCert queries CAA DNS records for every requested domain. If a record exists and excludes the chosen CA, you get an immediate `CaaException` naming the blocking domain — no wasted rate-limit attempt, no waiting for an ACME order to fail minutes later. The check follows RFC 8659 tree-walking, handles `issuewild` tags for wildcard domains, and respects parameter extensions (e.g. `letsencrypt.org; validationmethods=http-01`). CAA identifiers are built into every provider; `->skipCaaCheck()` opts out when DNS is internal or split-horizon.
58
+
Before submitting an order, CoyoteCert queries CAA DNS records for every requested domain. If a record exists and excludes the chosen CA, you get an immediate `CaaException` naming the blocking domain. No wasted rate-limit attempt, no waiting for an ACME order to fail minutes later. The check follows RFC 8659 tree-walking, handles `issuewild` tags for wildcard domains, and respects parameter extensions (e.g. `letsencrypt.org; validationmethods=http-01`). CAA identifiers are built into every provider; `->skipCaaCheck()` opts out when DNS is internal or split-horizon.
59
59
60
60
### Pre-flight self-test
61
61
62
-
Before asking the CA to validate, CoyoteCert does its own check first: it fetches the HTTP token or looks up the DNS TXT record itself. Misconfigured servers and propagation delays get caught before burning a rate-limit attempt.
62
+
Before asking the CA to validate, CoyoteCert does its own check first: it fetches the HTTP token or looks up the DNS TXT record itself. Misconfigured servers and propagation delays get caught before they cost you a rate-limit attempt. Look before you leap.
63
63
64
64
### 94 %+ test coverage with real CA integration tests
65
65
66
66
Every code path has unit tests with mocked responses. The integration suite runs against a live [Pebble](https://github.com/letsencrypt/pebble) server in CI across PHP 8.3, 8.4, and 8.5. No mock-only false confidence.
67
67
68
-
### CA-independent — no hidden defaults
68
+
### CA-independent: no hidden defaults
69
69
70
-
CoyoteCert has no default CA. Every issuance call requires you to pass a provider explicitly. Choosing a CA involves real trade-offs — trust store coverage, rate limits, certificate lifetime, EAB requirements, data residency — and that decision belongs to you, not the library.
70
+
CoyoteCert has no default CA. Every issuance call requires you to pass a provider explicitly. Choosing a CA involves real trade-offs (trust store coverage, rate limits, certificate lifetime, EAB requirements, data residency) and that decision belongs to you, not the library.
71
71
72
72
### Modern, idiomatic PHP
73
73
@@ -165,10 +165,10 @@ CoyoteCert ships with built-in providers for every major public ACME CA.
165
165
use CoyoteCert\Provider\LetsEncrypt;
166
166
use CoyoteCert\Provider\LetsEncryptStaging;
167
167
168
-
// Production — issues browser-trusted certificates
@@ -323,7 +323,7 @@ class Route53DnsPersist01Handler extends DnsPersist01Handler
323
323
324
324
Defined in [RFC 8737](https://datatracker.ietf.org/doc/html/rfc8737). The CA opens a TLS connection to port 443 of the domain and negotiates the `acme-tls/1` ALPN protocol. The server must present a self-signed certificate that contains a critical `id-pe-acmeIdentifier` extension (OID `1.3.6.1.5.5.7.1.31`) whose value is the SHA-256 digest of the key authorization. No port 80 access required.
325
325
326
-
Extend `TlsAlpn01Handler` and implement `deploy()` and `cleanup()`. Call `generateAcmeCertificate()` inside `deploy()` to obtain the certificate and key — it handles all the RFC 8737 encoding automatically.
326
+
Extend `TlsAlpn01Handler` and implement `deploy()` and `cleanup()`. Call `generateAcmeCertificate()` inside `deploy()` to obtain the certificate and key; it handles all the RFC 8737 encoding automatically.
327
327
328
328
```php
329
329
use CoyoteCert\Challenge\TlsAlpn01Handler;
@@ -351,7 +351,7 @@ class MyTlsAlpn01Handler extends TlsAlpn01Handler
351
351
->challenge(new MyTlsAlpn01Handler())
352
352
```
353
353
354
-
> **Note:** TLS-ALPN-01 validates on port 443 only and does not require port 80. It is supported by Caddy, nginx (with the ACME plugin), and HAProxy. Wildcard certificates are not supported — use DNS-01 for those.
354
+
> **Note:** TLS-ALPN-01 validates on port 443 only and does not require port 80. It is supported by Caddy, nginx (with the ACME plugin), and HAProxy. Wildcard certificates are not supported; use DNS-01 for those.
355
355
356
356
---
357
357
@@ -549,7 +549,7 @@ Register callbacks on the builder to react to certificate lifecycle events witho
549
549
550
550
### onIssued
551
551
552
-
Fires after every successful certificate issuance — whether first-time or a renewal.
552
+
Fires after every successful certificate issuance, first-time or renewal.
Fires only when an existing certificate is replaced — i.e. storage already held a cert before the new one was issued. Fires _after_`onIssued` callbacks.
571
+
Fires only when an existing certificate is replaced (i.e. storage already held a cert before the new one was issued). Fires after`onIssued` callbacks.
572
572
573
573
```php
574
574
CoyoteCert::with(new LetsEncrypt())
@@ -586,7 +586,7 @@ Both methods accept any `callable` and can be called multiple times. Callbacks r
586
586
587
587
## CAA pre-check
588
588
589
-
[CAA (Certification Authority Authorization)](https://en.wikipedia.org/wiki/DNS_Certification_Authority_Authorization) is a DNS record type that restricts which CAs are allowed to issue certificates for a domain. If `example.com` has `CAA 0 issue "digicert.com"`, Let's Encrypt will refuse the order — but only after you have consumed a rate-limit attempt and waited for the ACME workflow to fail.
589
+
[CAA (Certification Authority Authorization)](https://en.wikipedia.org/wiki/DNS_Certification_Authority_Authorization) is a DNS record type that restricts which CAs are allowed to issue certificates for a domain. If `example.com` has `CAA 0 issue "digicert.com"`, Let's Encrypt will refuse the order, but only after you have consumed a rate-limit attempt and waited for the ACME workflow to fail.
590
590
591
591
CoyoteCert runs the CAA check itself before submitting anything to the CA. If the records block the chosen CA, you get a `CaaException` immediately:
592
592
@@ -615,7 +615,7 @@ try {
615
615
616
616
`CaaException` extends `AcmeException`, so existing catch blocks for the base type continue to work.
617
617
618
-
IP address identifiers are excluded from the CAA check — CAA records apply to domain names only.
618
+
IP address identifiers are excluded from the CAA check; CAA records apply to domain names only.
All exceptions extend `AcmeException`, so a single `catch (AcmeException $e)` covers everything. Catch the narrower types when you need to react differently to specific failure modes.
639
639
640
-
### Rate limits — with Retry-After
640
+
### Rate limits: Retry-After included
641
641
642
642
```php
643
643
use CoyoteCert\Exceptions\RateLimitException;
@@ -663,7 +663,7 @@ use CoyoteCert\Exceptions\AuthException;
`->identifiers()` accepts IPv4 and IPv6 addresses alongside hostnames. CoyoteCert automatically sets `type: ip` on ACME identifiers and `IP:` SAN entries in the CSR — no extra API calls required.
737
+
`->identifiers()` accepts IPv4 and IPv6 addresses alongside hostnames. CoyoteCert automatically sets `type: ip` on ACME identifiers and `IP:` SAN entries in the CSR. No extra API calls required.
738
738
739
739
```php
740
740
// IPv4-only certificate (e.g. with Let's Encrypt shortlived profile)
IP SANs are validated via HTTP-01 (the CA connects to the IP directly). Wildcards cannot be combined with IP identifiers.
755
755
756
-
Not all CAs support IP SANs — check your CA's documentation. Let's Encrypt supports them on both the `classic` and `shortlived` profiles.
756
+
Not all CAs support IP SANs; check your CA's documentation. Let's Encrypt supports them on both the `classic` and `shortlived` profiles.
757
757
758
758
---
759
759
@@ -811,11 +811,11 @@ No configuration is required. CoyoteCert handles this transparently.
811
811
Profiles let you request a specific certificate type from the CA. Let's Encrypt currently supports two:
812
812
813
813
```php
814
-
->profile('shortlived') // 6-day certificate — no OCSP/CRL infrastructure needed
814
+
->profile('shortlived') // 6-day certificate, no OCSP/CRL infrastructure needed
815
815
->profile('classic') // 90-day certificate (default if no profile specified)
816
816
```
817
817
818
-
Short-lived certificates are renewed more frequently but eliminate the need for OCSP stapling, CRL checks, and revocation infrastructure. A significant operational simplification.
818
+
Short-lived certificates are renewed more frequently but eliminate the need for OCSP stapling, CRL checks, and revocation infrastructure. Much simpler to operate.
819
819
820
820
Profiles are forwarded to the CA only if the provider reports `supportsProfiles() === true`. For CAs that don't support profiles (ZeroSSL, Buypass, etc.) the setting is silently ignored, so you can call `->profile()` unconditionally.
If no alternate chain matches, CoyoteCert falls back to the default chain returned by the CA — so this call is always safe to include even when the CA offers only one chain.
839
+
If no alternate chain matches, CoyoteCert falls back to the default chain returned by the CA, so this call is always safe to include even when the CA offers only one chain.
840
840
841
841
When using the low-level API directly, pass the preference as a second argument to `getBundle()`:
0 commit comments