Search code examples
phpopensslcryptographycertificatecsr

PHP openssl_sign_csr with x509 attributes


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)


Solution

  • 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',
    )