# cert.cnf — openssl_csr_sign takes certificate extensions only from a config section[ req ]distinguished_name = req_dn [ req_dn ] [ server_cert ]subjectAltName = DNS:localhost, IP:127.0.0.1basicConstraints = critical, CA:FALSEkeyUsage = critical, digitalSignatureextendedKeyUsage = serverAuth<?php$options = ['config' => __DIR__ . '/cert.cnf', 'x509_extensions' => 'server_cert', 'digest_alg' => 'sha256']; $key = openssl_pkey_new(['private_key_type' => OPENSSL_KEYTYPE_EC, 'curve_name' => 'prime256v1']); // P-256$csr = openssl_csr_new(['commonName' => 'localhost'], $key, $options); // CA certificate null = self-signed; validity in days, counted from now$cert = openssl_csr_sign($csr, null, $key, 365, $options, random_int(1, PHP_INT_MAX));if ($cert === false) { throw new RuntimeException(openssl_error_string());} openssl_x509_export($cert, $certPem);openssl_pkey_export($key, $keyPem);file_put_contents('cert.pem', $certPem);file_put_contents('key.pem', $keyPem);How it works
openssl_pkey_newwithOPENSSL_KEYTYPE_ECandcurve_name => 'prime256v1'creates a P-256 key;openssl_csr_newbuilds a CSR forcommonName => 'localhost'.openssl_csr_sign($csr, null, $key, 365, $options, $serial)signs the request with its own key —nullas the CA certificate means self-signed — for 365 days from now.x509_extensions => 'server_cert'names the cert.cnf section with subjectAltName, basicConstraints, keyUsage and extendedKeyUsage;digest_alg => 'sha256'sets the signature hash.openssl_x509_exportandopenssl_pkey_exportreturn the PEM strings for cert.pem and key.pem.
Gotchas
- The serial number defaults to 0 (
serial=00in OpenSSL's output), which RFC 5280 does not allow — serials must be positive. Pass a random one, such asrandom_int(1, PHP_INT_MAX). - Without
digest_alg, the certificate is signed with SHA-1 (ecdsa-with-SHA1); pass'digest_alg' => 'sha256'. - Without
x509_extensions, and with a config whose[ req ]section names no default, the certificate has no extensions at all — no SANs and no basicConstraints. - A misspelt section name does not throw:
openssl_csr_signreturnsfalsewith only a warning (Error loading extensions_section section missing_section of c.cnf), so check the result before exporting.