PHP openssl_sign_csr with x509 attributes

134 views Asked by At

I have a script that generates CSRs and can integrate with windows CA to sign, but I want to have the option to sign with a local CA, so I was using the openssl_sign_csr function. And it is signing the certificates, however, it does not retain the x509 attributes (namely, extended key usage and subject alternative names).

I pass it the config file and I've tried a few samples that I've found on the internet, the key things that im including is the copy_extensions = copy and x509_extensions = usr_cert with the usr_cert block having:

basicConstraints=CA:FALSE
nsComment = "OpenSSL Generated Certificate"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
subjectAltName=email:copy

however, any mix of config that doesn't result in an error does not carry across any of the extensions. the above applied the email field to the SAN instead. i cannot find a copy option. but also, I'd like to have it apply the CN field if the SAN is not specified.

I know the CSR has them because when signed by certsrv it carries them.

Would love some insight from anyone that has gotten this working in the past. (also, key usage will be for server identification, rarely client identification, if that makes any difference to it)

3

There are 3 answers

4
Olivier On BEST ANSWER

The copy_extensions option can be used with the command-line version of OpenSSL. It doesn't seem possible to use it with the PHP OpenSSL extension.

As a workaround, you can extract the informations you need from the CSR. It is possible to do so with the help of the phpseclib library (as mentioned here):

require('vendor/autoload.php');
use phpseclib3\File\X509;

function getCsrData(string $csrFile): array
{
    $csr = file_get_contents($csrFile);
    if(!$csr)
        throw new Exception('CSR not found');

    $x509 = new X509();
    if(!$x509->loadCSR($csr))
        throw new Exception('Invalid CSR');

    // Get the Common Name
    $data['CN'] = $x509->getDNProp('CN')[0];

    // Get the Subject Alternative Name
    $SAN = $x509->getExtension('id-ce-subjectAltName');
    if($SAN)
    {
        $arr = [];
        foreach($SAN as $val)
            $arr[] = 'DNS:' . $val['dNSName'];
        $data['subjectAltName'] = implode(', ', $arr);
    }

    // Get the Extended Key Usage
    $XKU = $x509->getExtension('id-ce-extKeyUsage');
    if($XKU)
    {
        $arr = [];
        foreach($XKU as $val)
            $arr[] = substr($val, 6);
        $data['extKeyUsage'] = implode(', ', $arr);
    }

    return $data;
}

Example of a CSR generation:

openssl req -new -sha256 -nodes -newkey rsa:2048 -keyout test.key -out test.csr -config test.cnf

with the following config file (test.cnf):

[req]
distinguished_name = dn
req_extensions = req_ext
prompt = no

[dn]
CN = www.domain.com

[req_ext]
subjectAltName = DNS:www.domain.com, DNS:domain.com
extendedKeyUsage = serverAuth

The CSR is parsed like this:

$data = getCsrData('test.csr');
var_export($data);

Output:

array (
  'CN' => 'www.domain.com',
  'subjectAltName' => 'DNS:www.domain.com, DNS:domain.com',
  'extKeyUsage' => 'serverAuth',
)
2
Fabio William Conceição On

See if this helps you out:

<?php


$csr = file_get_contents('<your csr path and file>.csr');


$config = array(
    'config' => '<openssl path configuration file>.cnf', // openssl config path
    'digest_alg' => 'sha256', // hash algo
    'x509_extensions' => 'usr_cert', // X.509 extension
    'req_extensions' => 'req_ext', //  CSR extension
    'private_key_bits' => 2048, // Key size
);


$privateKey = openssl_pkey_new($config);


$signedCert = openssl_csr_sign($csr, null, $privateKey, 365, $config, time());


openssl_x509_export($signedCert, $certOut);


echo $certOut;

?>
1
mbuckner On

Sometimes it may just be an issue with getting the right extensions configured. For example copy_extensions = copy and x509_extensions = usr_cert settings should be under the "CA_default" section of your config file.

If's usually the small things that get you, e.g., openssl_csr_sign won't work right if you don't have a valid openssl.cnf installed for this function.

And if all else fails, there's always Command-Line OpenSSL, e.g.,

openssl x509 -req -in yourcsr.csr -CA yourca.crt -CAkey yourcakey.key -CAcreateserial -out yourcert.crt -days 365 -extfile yourconfig.cnf -extensions usr_cert

You can also manually extract the attributes from the CSR and include them in the signing command. Use the openssl req -noout -text -in yourcsr.csr command to view the attributes in the CSR and then manually add them to the yourconfig.cnf file under the "usr_cert" section.

It could be helpful to try alternative libraries like phpseclib, e.g., use the setExtensionCopy(true) method to ensure your x509 extensions are copied from the CSR to the signed certificate correctly.