# csr.cnf — openssl_csr_new takes extensions only from a config section[ req ]distinguished_name = req_dnreq_extensions = v3_req [ req_dn ] [ v3_req ]subjectAltName = DNS:api.example.com, DNS:www.example.com<?php$key = openssl_pkey_new(['private_key_type' => OPENSSL_KEYTYPE_EC, 'curve_name' => 'prime256v1']); // P-256 $csr = openssl_csr_new( ['countryName' => 'US', 'organizationName' => 'Example Corp', 'commonName' => 'api.example.com'], $key, ['config' => __DIR__ . '/csr.cnf', 'req_extensions' => 'v3_req', 'digest_alg' => 'sha256'],);if ($csr === false) { throw new RuntimeException(openssl_error_string());} openssl_csr_export($csr, $csrPem);openssl_pkey_export($key, $keyPem); // PKCS#8; pass a passphrase to encrypt itfile_put_contents('csr.pem', $csrPem);file_put_contents('key.pem', $keyPem);How it works
openssl_pkey_newwithOPENSSL_KEYTYPE_ECandcurve_name => 'prime256v1'creates the key (prime256v1 is OpenSSL's name for P-256).- The first argument of
openssl_csr_newis the subject; keys are OpenSSL's long names (countryName,organizationName,commonName). configpoints at csr.cnf andreq_extensionsnames itsv3_reqsection, which holdssubjectAltName.openssl_csr_exportandopenssl_pkey_exportreturn the PEM strings;openssl_pkey_exportwrites PKCS#8.
Gotchas
- Without
digest_alg, the request is signed with SHA-1 (ecdsa-with-SHA1in OpenSSL's output). CAs reject such CSRs; always pass'digest_alg' => 'sha256'. - There is no option for SANs in the PHP API: without a config section, the CSR has only a CN, which modern clients do not accept for host names.
openssl_csr_newreturnsfalseon failure without throwing; check the result and readopenssl_error_string()for the reason.- For dynamic SAN lists, either write the config file at runtime or put
subjectAltName = $ENV::SANin it and setSAN=DNS:a.example.com,DNS:b.example.comin the environment — OpenSSL expands it when it reads the section.