Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ssl Laravel Package

acmephp/ssl

acmephp/ssl is a PHP library for SSL/TLS certificate handling. It provides key and CSR generation, certificate parsing and signing, and the low-level certificate utilities that power Acme PHP's Let's Encrypt client.

View on GitHub
Deep Wiki
Context7

Getting Started

acmephp/ssl is the low-level SSL primitive layer of the Acme PHP stack. It does not talk to Let's Encrypt — it gives you typed objects for keys, CSRs, certificates and a thin, opinionated wrapper over ext-openssl so you never touch raw openssl_* resource juggling.

composer require acmephp/ssl

Requirements: PHP with ext-openssl. There is no Laravel service provider, config file or facade — it's a plain library (AcmePhp\Ssl\*), so you wire it yourself.

Read these four files first (they cover 90% of the surface area):

File Why
src/KeyPair.php / src/Key.php Key generation, PEM export, public key extraction
src/CertificateSigningRequest.php Building + exporting a CSR
src/Signer/OpenSslSigner.php Actually signing a CSR
src/Parser/OpenSslParser.php Turning a PEM string into a Certificate/Key/CSR object

First use case — almost every project starts here: given a certificate PEM you already have on disk, answer "when does it expire, and what domains does it cover?"

use AcmePhp\Ssl\Parser\OpenSslParser;

$parser = new OpenSslParser();
$cert   = $parser->parseCertificate($pem);

$cert->getSubjectCommonName();       // string
$cert->getSubjectAlternativeNames(); // array of DNS names
$cert->getValidTo();                 // \DateTime (UTC)
$cert->getSerialNumber();            // hex string

Second use case: minting a self-signed certificate for local TLS/mTLS without shelling out to openssl:

use AcmePhp\Ssl\Generator\KeyOption;
use AcmePhp\Ssl\Generator\OpenSslKeyGenerator;
use AcmePhp\Ssl\Signer\OpenSslSigner;

$generator = new OpenSslKeyGenerator();
$keyPair   = $generator->generateKeyPair(new KeyOption(KeyOption::KEY_RSA, 4096));

$signer = new OpenSslSigner();
$certificate = $signer->signCertificate(
    $csr,
    null,           // no issuer certificate => self-signed
    $keyPair,
    new \DateTime('-1 day'),
    new \DateTime('+1 year')
);

Implementation Patterns

Register the collaborator objects as singletons. They're stateless and cheap, but you want one place to swap in a fake for tests.

// app/Providers/SslServiceProvider.php
$this->app->singleton(OpenSslParser::class);
$this->app->singleton(OpenSslSigner::class);
$this->app->singleton(OpenSslKeyGenerator::class);

$this->app->alias(OpenSslParser::class, ParserInterface::class);
$this->app->alias(OpenSslSigner::class, SignerInterface::class);

Depend on ParserInterface / SignerInterface in your services, not the concrete classes — those interfaces are the intended extension point.

Store PEMs in a text column, with the private key encrypted. Casts are the natural fit:

protected $casts = [
    'certificate_pem' => 'string',
    'private_key_pem' => 'encrypted',  // Laravel's Crypt under the hood
];

Never let getPEM() output reach logs, ->toArray(), or Horizon payloads. Wrap any Key/KeyPair you pass into a queued job in a DTO that only carries what the job needs, or serialize the PEM inside Crypt::encryptString() first.

Renewal checks as a scheduled command. getValidTo() returns a plain \DateTime in UTC; comparing against now() without normalising the timezone is the classic off-by-hours bug.

$expiresAt = \Carbon\CarbonImmutable::instance($cert->getValidTo());

if ($expiresAt->isBefore(now()->addDays(30))) {
    $this->line("Renewing {$cert->getSubjectCommonName()}");
}

A daily Schedule::command('certs:check')->at('03:00') that loops over stored certificates is the whole feature.

Domain coverage check — useful in middleware or an uptime-ish check:

$sans = $cert->getSubjectAlternativeNames(); // ['example.com', 'www.example.com']
$matches = fn (string $host) => in_array($host, $sans, true)
    || collect($sans)->contains(fn ($san) => str_starts_with($san, '*.')
        && str_ends_with($host, substr($san, 1)));

Note wildcards only match one label — *.example.com must not match a.b.example.com. Do that check yourself; the library won't.

Building a CSR for an ACME order. Build the DistinguishedName with the common name, attach the SANs (this is what the CA actually validates — the CN is cosmetic since BR requirements changed), then hand it to the signer:

use AcmePhp\Ssl\DistinguishedName;
use AcmePhp\Ssl\CertificateSigningRequest;

$dn = new DistinguishedName(
    $primaryDomain,      // common name
    'US',                // country
    'Acme Inc',          // organization
    'IT',                // organizational unit
    '[email protected]'  // email
);
// SANs: pass as the last constructor arg or via setSubjectAlternativeNames(),
// depending on the version you pin — verify against vendor/ before relying on it.

$csr = new CertificateSigningRequest($dn, $keyPair->getPublicKey());
$csr = $signer->signCertificateSigningRequest($csr, $keyPair->getPrivateKey());

$pem = $csr->getPEM(); // base64url-encode this for the ACME newOrder payload

Pairing with the rest of the stack. acmephp/ssl is the bottom layer — the actual protocol work lives in acmephp/acme-lib (account, order, challenge, finalize). If you're in Laravel and just want certificates issued, use a higher-level client and keep this package for the parsing/signing primitives, or skip it entirely and use openssl_* directly for the trivial cases.

Key choice. RSA 2048/4096 is the boring, universally-supported option; KeyOption::KEY_EC with a P-256 curve produces much smaller certs and faster handshakes, but verify your ext-openssl build actually exposes the curve before shipping — older builds silently fail.

Gotchas and Tips

It is unmaintained. Last release January 2019, ~15 stars, zero dependents. Treat it as frozen code you own. The biggest practical consequence: code written against PHP 7-era ext-openssl may assume openssl_pkey_get_* returns a resource. PHP 8 returns an OpenSSLAsymmetricKey object. Any is_resource() guard, strict type hint, or get_resource_type() call in Key/KeyPair internals is a landmine. Test the key-round-trip path on your target PHP version before committing.

getPEM() returns the private key unencrypted, even if you loaded it from a passphrase-protected file. Encryption at rest is entirely your problem (hence the encrypted cast above). There's no "encrypted PEM" object.

getSerialNumber() is a hex string, not an integer. If you're matching against a CA's dashboard serial, normalise case and strip colons. Certificates with serials > PHP_INT_MAX will silently truncate if you cast to int.

SANs come back as bare DNS names from Certificate, but as DNS:foo-prefixed strings if you fall back to raw openssl_x509_parse(). Don't mix the two parsing paths in one codebase — pick the library parser and stay there.

No validation is performed. parseCertificate() will happily parse an expired, self-signed, wrong-host certificate. There's no chain verification, no OCSP/CRL, no issuer trust check. If you need "is this cert actually valid for this host right now", that's openssl_x509_checkpurpose or an HTTP client doing the handshake — this package won't tell you.

Multi-certificate PEMs need manual splitting. A full chain from a CA (leaf + intermediate) is one blob with multiple -----BEGIN CERTIFICATE----- blocks. The parser handles one certificate; split on the BEGIN marker with a regex and parse each block, then keep the last as the issuer for signing.

\DateTime objects are mutable. getValidTo() returns the object the parser built; mutating it corrupts the certificate object's internal state for any later caller. Clone before doing arithmetic, or wrap in CarbonImmutable::instance().

Exceptions are library-specific — AcmePhp\Ssl\Exception\AcmeSslException and friends, not \RuntimeException. Catch AcmeSslException broadly in controllers, but let it bubble in console commands so failures are loud; a silently-swallowed parse failure means an expired cert on production.

Testing. Generate fixtures once (openssl req -x509 -newkey rsa:2048 -nodes ...) and commit the PEMs rather than generating them in setUp(). Key generation is slow (hundreds of ms for RSA 4096), and a suite that regenerates keys per test will drag.

Extension points worth knowing: implement SignerInterface if you need to delegate signing to a KMS/HSM instead of a local private key; implement ParserInterface if you must support a format ext-openssl chokes on. Everything else is final-ish value objects — don't subclass Certificate, construct it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle