composer/ca-bundle
Utility to locate the system CA root bundle for TLS/SSL verification, with a bundled Mozilla CA bundle fallback. Provides helpers to validate CA files and integrate easily with cURL, PHP streams, and Guzzle.
Install the package:
composer require composer/ca-bundle
Add to composer.json under require if not using Composer directly.
First use case:
Use CaBundle::getSystemCaRootBundlePath() in your HTTP client initialization (e.g., Guzzle, cURL, or Laravel HTTP client). Example:
use Composer\CaBundle\CaBundle;
$caPath = CaBundle::getSystemCaRootBundlePath();
Where to look first:
cacert.pem expiry dates).$client = new GuzzleHttp\Client([
GuzzleHttp\RequestOptions::VERIFY => CaBundle::getSystemCaRootBundlePath(),
]);
$ch = curl_init('https://example.com');
$caPath = CaBundle::getSystemCaRootBundlePath();
curl_setopt($ch, CURLOPT_CAINFO, is_dir($caPath) ? null : $caPath);
curl_setopt($ch, CURLOPT_CAPATH, is_dir($caPath) ? $caPath : null);
$response = Http::withOptions([
'verify' => CaBundle::getSystemCaRootBundlePath(),
])->get('https://api.example.com');
Use for file_get_contents() or stream_context_create():
$context = stream_context_create([
'ssl' => [
'cafile' => is_file($caPath) ? $caPath : null,
'capath' => is_dir($caPath) ? $caPath : null,
],
]);
file_get_contents('https://example.com', false, $context);
Explicitly check for system CA availability and log fallbacks:
$caPath = CaBundle::getSystemCaRootBundlePath();
if (str_starts_with($caPath, __DIR__ . '/vendor/composer/ca-bundle')) {
Log::warning('Using bundled CA bundle (fallback)');
}
Verify CA file integrity before use:
if (!CaBundle::validateCaFile($caPath)) {
throw new RuntimeException('Invalid CA bundle');
}
Override default paths in bootstrap/app.php or a service provider:
CaBundle::reset(); // Clear cache
CaBundle::setDefaultCaPath('/custom/ca/path'); // Extend CaBundle class or use dependency injection
Service Provider Binding: Bind the CA path to the container for reusable HTTP clients:
public function register()
{
$this->app->singleton('ca.path', function () {
return CaBundle::getSystemCaRootBundlePath();
});
}
Use in HTTP clients:
$client = new GuzzleHttp\Client([
GuzzleHttp\RequestOptions::VERIFY => $this->app['ca.path'],
]);
Configurable CA Path:
Add to config/app.php:
'ca_bundle' => [
'path' => env('CA_BUNDLE_PATH', null), // Override via .env
],
Use in a helper:
function getCaPath(): string
{
return config('ca_bundle.path') ?? CaBundle::getSystemCaRootBundlePath();
}
Mock System CA Paths:
Use CaBundle::reset() and mock getSystemCaRootBundlePath() in tests:
CaBundle::reset();
$this->partialMock(CaBundle::class, function ($mock) {
$mock->method('getSystemCaRootBundlePath')->willReturn('/mock/ca/path');
});
Validate Fallback Behavior:
Test with CaBundle::getBundledCaBundlePath() to ensure fallback works:
$this->assertStringContainsString('cacert.pem', CaBundle::getBundledCaBundlePath());
Path Detection Quirks:
false if no system CA is found. Always check return type:
$caPath = CaBundle::getSystemCaRootBundlePath();
if ($caPath === false) {
throw new RuntimeException('No CA bundle found');
}
$caPath = CaBundle::getBundledCaBundlePath(); // Explicit fallback
openssl_x509_parse Safety:
CaBundle::isOpensslParseSafe() checks if PHP’s openssl_x509_parse is enabled and safe to use. If false, avoid validateCaFile():
if (!CaBundle::isOpensslParseSafe()) {
Log::warning('Skipping CA validation due to unsafe openssl_x509_parse');
}
Static Cache:
CaBundle caches results statically. Reset with CaBundle::reset() after environment changes (e.g., Docker builds):
CaBundle::reset(); // Clear cache for dynamic environments
Bundled CA Expiry:
cacert.pem expires ~1 year from release date (e.g., 2026-02-11 for v1.5.11). Monitor updates via releases.OpenSSL Version Mismatches:
openssl_x509_parse may fail. Use CaBundle::getBundledCaBundlePath() as a fallback.Log CA Paths: Add debug logs to verify paths:
Log::debug('System CA Path:', ['path' => CaBundle::getSystemCaRootBundlePath()]);
Log::debug('Bundled CA Path:', ['path' => CaBundle::getBundledCaBundlePath()]);
Validate CA File: Manually check the CA file format:
openssl x509 -in vendor/composer/ca-bundle/cacert.pem -text -noout
Check PHP OpenSSL:
Ensure OpenSSL is enabled in php.ini:
if (!extension_loaded('openssl')) {
throw new RuntimeException('OpenSSL extension is required');
}
Custom CA Paths:
Extend the CaBundle class to add custom logic:
class CustomCaBundle extends \Composer\CaBundle\CaBundle
{
public static function getCustomCaPath(): string
{
return '/etc/ssl/certs/custom-ca.pem';
}
public static function getSystemCaRootBundlePath(): ?string
{
$path = parent::getSystemCaRootBundlePath();
return $path ?: self::getCustomCaPath();
}
}
Override Default Trust Stores:
Modify the getTrustStorePaths() method (internal) by copying the class and overriding:
protected static function getTrustStorePaths(): array
{
return [
'/custom/trust/store/path',
// ... other paths
];
}
Dynamic CA Updates: Use a cron job or Laravel scheduler to update the bundled CA periodically:
// In a console command
public function handle()
{
$caPath = CaBundle::getBundledCaBundlePath();
if (filemtime($caPath) < strtotime('-30 days')) {
// Trigger a CA update (e.g., via Composer or custom script)
}
}
Forge/Docker Environments: If using Laravel Forge or Docker, ensure the container has access to host CA paths or use the bundled fallback:
# Example Dockerfile snippet
RUN apt-get update && apt-get install -y ca-certificates
Homestead/Vagrant: Shared
How can I help you explore Laravel packages today?