Invalid request when trying to use my CSR in compliance

The first thing I notice is that the CSR length is shorter for me than what is in the integration sandbox (Zatca)

the sandbox csr looks like:

{
  "csr": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ0ZUQ0NBYndDQVFBd2RURUxNQWtHQTFVRUJoTUNVMEV4RmpBVUJnTlZCQXNNRFZKcGVXRmthQ0JDY21GdQpZMmd4SmpBa0JnTlZCQW9NSFUxaGVHbHRkVzBnVTNCbFpXUWdWR1ZqYUNCVGRYQndiSGtnVEZSRU1TWXdKQVlEClZRUUREQjFVVTFRdE9EZzJORE14TVRRMUxUTTVPVGs1T1RrNU9Ua3dNREF3TXpCV01CQUdCeXFHU000OUFnRUcKQlN1QkJBQUtBMElBQktGZ2ltdEVtdlJTQkswenI5TGdKQXRWU0NsOFZQWno2Y2RyNVgrTW9USG84dkhOTmx5Vwo1UTZ1N1Q4bmFQSnF0R29UakpqYVBJTUo0dTE3ZFNrL1ZIaWdnZWN3Z2VRR0NTcUdTSWIzRFFFSkRqR0IxakNCCjB6QWhCZ2tyQmdFRUFZSTNGQUlFRkF3U1drRlVRMEV0UTI5a1pTMVRhV2R1YVc1bk1JR3RCZ05WSFJFRWdhVXcKZ2FLa2daOHdnWnd4T3pBNUJnTlZCQVFNTWpFdFZGTlVmREl0VkZOVWZETXRaV1F5TW1ZeFpEZ3RaVFpoTWkweApNVEU0TFRsaU5UZ3RaRGxoT0dZeE1XVTBORFZtTVI4d0hRWUtDWkltaVpQeUxHUUJBUXdQTXprNU9UazVPVGs1Ck9UQXdNREF6TVEwd0N3WURWUVFNREFReE1UQXdNUkV3RHdZRFZRUWFEQWhTVWxKRU1qa3lPVEVhTUJnR0ExVUUKRHd3UlUzVndjR3g1SUdGamRHbDJhWFJwWlhNd0NnWUlLb1pJemowRUF3SURSd0F3UkFJZ1NHVDBxQkJ6TFJHOApJS09melI1L085S0VicHA4bWc3V2VqUlllZkNZN3VRQ0lGWjB0U216MzAybmYvdGo0V2FxbVYwN01qZVVkVnVvClJJckpLYkxtUWZTNwotLS0tLUVORCBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0K"
}

whereas mines looks like:

{
  "csr": 
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlIOE1JR2tBZ0VBTUVVeEN6QUpCZ05WQkFZVEFrRlZNUk13RVFZRFZRUUlEQXBUYjIxbExWTjBZWFJsTVNFdwpId1lEVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdRd1ZqQVFCZ2NxaGtqT1BRSUJCZ1VyCmdRUUFDZ05DQUFSSDNUL1dCUnptcmoweUc2cGptTi9OYitlTC9ibEsxWUZ2T0V0UFhya1BOcEZzU2NtMUJ5OXAKMHo3QWVJelNOTXhjTFA0UXAwYmpLS2cwOWh4TVZVVHRvQUF3Q1FZSEtvWkl6ajBFQVFOSUFEQkZBaUVBenFJUgp5WEhIaDI0b0U5VW9CU1N1dWsydjRrd3k1UWwrdDhscXc5bC9CVFlDSUVRRWVCY1V3R2dBWGVBazRMNVQ1YjNVClVjek5XZTlrc3pxYlo5VXUzeHZJCi0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo=
}

Should I be concerned about this?
Also because this is sandbox I am using dummy data eg:

        $config_data = array(
            'emailAddress' => 'test_email@gmail.com',
            'commonName' => 'mydomain.com',
            'country' => 'SA',
            'organizationalUnitName' => 'Testing Branch',
            'organizationName' => 'Test Company',
            'serialNumber' => '1-Model|2-3492842|3-49182743421',
            'vatNumber' => '317460736806263',
            'invoiceType' => '1100',
            'registeredAddress' => 'TestAddress',
            'businessCategory' => 'Software Development'
        );

        define('CONFIG_CNF_FILE_TEMPLATE', "oid_section=OIDS
[ OIDS ]
certificateTemplateName= 1.3.6.1.4.1.311.20.2
[req]
default_bits=2048
emailAddress=__emailAddress
req_extensions=v3_req
x509_extensions=v3_Ca
prompt=no
default_md=sha256
req_extensions=req_ext
distinguished_name=dn
[dn]
CN=__commonName
C=__country
OU=__organizationalUnitName
O=__organizationName
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[req_ext]
certificateTemplateName = ASN1:PRINTABLESTRING:PREZATCA-code-Signing
subjectAltName = dirName:alt_names
[alt_names]
SN=__serialNumber
UID=__vatNumber
title=__invoiceType
registeredAddress=__registeredAddress
businessCategory=__businessCategory
# [v3_Ca]
# subjectKeyIdentifier=hash
# authorityKeyIdentifier=keyid:always,issue");


        $template = CONFIG_CNF_FILE_TEMPLATE;
        foreach ($config_data as $key => $value) {
            $configCnf = str_replace("__$key", $value, $template);
            $template = $configCnf;
        }

This is in PHP. Any help is appreciated. Note: Even if I use the proper data it still gives me invalid request. (The length of the CSR is still shorter)

Also is there anyway to get a more verbose error response than just $response string (15) "Invalid Request"

It would be clearer if you show the generated config.cnf content and how you generated the privateKey and CSR.

And for sanbox / NonProduction Environment you should use default Vat Number 399999999900003

Sure! Here’s the generated config.conf file contents:

oid_section=OIDS
[ OIDS ]
certificateTemplateName= 1.3.6.1.4.1.311.20.2
[req]
default_bits=2048
emailAddress=test_email@gmail.com
req_extensions=v3_req
x509_extensions=v3_Ca
prompt=no
default_md=sha256
req_extensions=req_ext
distinguished_name=dn
[dn]
CN=Mohd Arafat Hossain
C=SA
OU=Demora Branch
O=Test Company
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[req_ext]
certificateTemplateName = ASN1:PRINTABLESTRING:PREZATCA-code-Signing
subjectAltName = dirName:alt_names
[alt_names]
SN=1-Model|2-3492842|3-49182743421
UID=317460736806263
title=1100
registeredAddress=TestAddress
businessCategory=Software Development
# [v3_Ca]
# subjectKeyIdentifier=hash
# authorityKeyIdentifier=keyid:always,issue

Here’s the way I’m doing generating the Private key, public key, CSR, CSID etc.

    public function csid_attempt()
    {

        //===================== START: PUBLIC KEY & PRIVATE KEY =================================

        $privateKeyPath = WRITEPATH . 'keys/PrivateKey.pem';
        $publicKeyPath  = WRITEPATH . 'keys/PublicKey.pem';

        define('PRIVATE_KEY_OPTIONS', [
            'private_key_type' => OPENSSL_KEYTYPE_EC,
            'curve_name' => 'secp256k1',
        ]);

        if (!function_exists('str_starts_with')) {
            function str_starts_with($haystack, $needle)
            {
                return substr($haystack, 0, strlen($needle)) === $needle;
            }
        }

        define('SEP', str_starts_with(PHP_OS, 'WIN') ? "\\" : "/");

        // Generate Private Key
        $privateKeySSL = openssl_pkey_new(PRIVATE_KEY_OPTIONS); // PRIVATE_KEY_OPTIONS is in const.php
        openssl_pkey_export($privateKeySSL, $privateKeyStr);

        // Generate Public Key
        $details = openssl_pkey_get_details($privateKeySSL);
        $publicKey = $details['key'];

        // Store Private Key in a .pem File
        // $privateKeyFile = fopen('PrivateKey.pem', 'w');

        // Store Private Key in a .pem File
        $privateKeyFile = fopen($privateKeyPath, 'w');
        fwrite($privateKeyFile, str_replace("PRIVATE", "EC PRIVATE", $privateKeyStr));
        fclose($privateKeyFile);

        // Store Public Key in a .pem File
        $publicKeyFile = fopen($publicKeyPath, 'w');
        fwrite($publicKeyFile, str_replace("PRIVATE", "EC PRIVATE", $publicKey));
        fclose($publicKeyFile);

        $all_keys = array(
            'privateKey' => $privateKeyStr,
            // 'privateKeyPath' => __DIR__ . SEP . "PrivateKey.pem",
            'privateKeyPath' => $privateKeyPath,
            'publicKey' => $publicKey,
            'publicKeyPath' => $publicKeyPath,
            'privateKeySSL' => $privateKeySSL
        );

        //OUTPUT: Private key, Public key, Paths of private key and public key
        // dd($all_keys);

        //===================== END: PUBLIC KEY & PRIVATE KEY =================================


        //======================== START: CONFIG FILE =========================================

        $configFilePath  = WRITEPATH . 'keys/config.cnf';

        $config_data = array(
            'emailAddress' => 'test_email@gmail.com',
            'commonName' => 'Mohd Arafat Hossain',
            'country' => 'SA',
            'organizationalUnitName' => 'Testing Branch',
            'organizationName' => 'Test Company',
            'serialNumber' => '1-Model|2-3492842|3-49182743421',
            'vatNumber' => '399999999900003',
            'invoiceType' => '1100',
            'registeredAddress' => 'TestAddress',
            'businessCategory' => 'Software Development'
        );

        define('CONFIG_CNF_FILE_TEMPLATE', "oid_section=OIDS
[ OIDS ]
certificateTemplateName= 1.3.6.1.4.1.311.20.2
[req]
default_bits=2048
emailAddress=__emailAddress
req_extensions=v3_req
x509_extensions=v3_Ca
prompt=no
default_md=sha256
req_extensions=req_ext
distinguished_name=dn
[dn]
CN=__commonName
C=__country
OU=__organizationalUnitName
O=__organizationName
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[req_ext]
certificateTemplateName = ASN1:PRINTABLESTRING:PREZATCA-code-Signing
subjectAltName = dirName:alt_names
[alt_names]
SN=__serialNumber
UID=__vatNumber
title=__invoiceType
registeredAddress=__registeredAddress
businessCategory=__businessCategory
# [v3_Ca]
# subjectKeyIdentifier=hash
# authorityKeyIdentifier=keyid:always,issue");


        $template = CONFIG_CNF_FILE_TEMPLATE;
        foreach ($config_data as $key => $value) {
            $configCnf = str_replace("__$key", $value, $template);
            $template = $configCnf;
        }

        // $configCnfFile = fopen('config.cnf', 'w');
        $configCnfFile = fopen($configFilePath, 'w');
        fwrite($configCnfFile, $configCnf);
        fclose($configCnfFile);

        $all_config_info = array(
            'data' => $configCnf,
            // 'path' => __DIR__ . SEP . "config.cnf"
            'path' => $configFilePath
        );

        //OUTPUT: Config info
        // dd($all_config_info);

        //======================== END: CONFIG FILE =========================================

        //======================== START: CSR Generate =========================================


        $csrPath  = WRITEPATH . 'keys/csr.pem';
        $encodedCsrPath  = WRITEPATH . 'keys/csr_encoded.pem';

        $privateKeyPath = $all_keys['privateKeyPath'];
        $configCnfPath = $all_config_info['path'];
        $outputFileName = 'taxpayer';
        // $privateKeyPath = str_replace('\\', '/', $privateKeyPath);
        // $configCnfPath = str_replace('\\', '/', $configCnfPath);

        //ORIGINAL COMMAND
        // shell_exec("openssl req -new -sha256 -key \"$privateKeyPath\" -extensions v3_req -config \"$configCnfPath\" -out $outputFileName.csr");

        // Generate a new CSR
        $privateKeyResource = openssl_pkey_get_private($all_keys['privateKey']);
        $dn = $this->parseConfigFile($configCnfPath);
        $csrResource = openssl_csr_new($dn, $privateKeyResource);
        // Export CSR to a variable
        openssl_csr_export($csrResource, $csrOut);

        //Write CSR to file
        $csrFile = fopen($csrPath, 'w');
        fwrite($csrFile, $csrOut);
        fclose($csrFile);

        // $csrFile = fopen("$outputFileName.csr", 'r'); //ORIGINAL
        $csrFile = fopen($csrPath, 'r');
        // $csr = fread($csrFile, filesize("$outputFileName.csr")); //ORIGINAL
        $csr = fread($csrFile, filesize($csrPath));
        $csrEncoded = base64_encode($csr);
        fclose($csrFile);


        // //TESTING COMMAND
        // $command_csrPath  = WRITEPATH . 'keys/taxpayer.csr';
        // $command_csrFile = fopen($command_csrPath, 'r');
        // $command_csr = fread($command_csrFile, filesize($command_csrPath));
        // $command_test = base64_encode($command_csr);
        // dd($command_test);


        $all_csr_info = array(
            'utf8' => $csr,
            'base64' => $csrEncoded
        );

        //Write CSR to file
        $encodedCsrFile = fopen($encodedCsrPath, 'w');
        fwrite($encodedCsrFile, $all_csr_info['base64']);
        fclose($encodedCsrFile);

        $encodedCsr = $all_csr_info['base64'];

        $csrData['csr'] = $encodedCsr;
        $postData = json_encode($csrData);
        // print_r($postData);



        //OUTPUT: CSR

        // dd($all_csr_info);

        //======================== END: CSR Generate =========================================


        //======================== START: CSID Generate =========================================

        define('ZATCA_ORIGIN', 'https://gw-fatoora.zatca.gov.sa/e-invoicing/developer-portal');

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, ZATCA_ORIGIN . "/compliance");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'accept: application/json',
            'OTP: 123345',
            'Accept-Version: V2',
            'Content-Type: application/json',
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);

        // curl_setopt($ch, CURLOPT_VERBOSE, true);
        // $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        $response = curl_exec($ch);

        dd($response);

        // $responseData = json_decode($response->getBody(), true);

        curl_close($ch);
        return json_decode($response, true);


        //======================== END: CSID Generate =========================================

I don’t know php very well, so I can’t understand your code. Maybe you can try this code from AI. it simple code but works well on my computer.

Let see if this work for you

<?php

 // Generate EC private key
 function generateEcPrivateKey($outputFilePath) {
    $cmd = "openssl ecparam -name secp256k1 -genkey -noout -out " . escapeshellarg($outputFilePath);
    exec($cmd, $output, $returnVar);

    if ($returnVar !== 0) {
        die("Error generating EC private key: " . implode("\n", $output));
    }
}

// Generate CSR
function generateCsr($privateKeyFilePath, $configPath, $csrOutputFilePath) {
    $cmd = "openssl req -new -sha256 -key " . escapeshellarg($privateKeyFilePath) . 
        " -config " . escapeshellarg($configPath) . 
        " -out " . escapeshellarg($csrOutputFilePath);

    exec($cmd, $output, $returnVar);
    
    if ($returnVar !== 0) {
        die("Error generating CSR: " . implode("\n", $output));
    }

    // Read the CSR content and encode it in Base64
    $csrContent = file_get_contents($csrOutputFilePath);
    $csrContent = base64_encode($csrContent);

    file_put_contents($csrOutputFilePath, $csrContent);

    return $csrContent;
}

function complianceCSID($csr, $OTP, $url) {

    $jsonPayload = json_encode([
        'csr' => $csr
    ]);

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'accept: application/json',
        'accept-language: en',
        "OTP: $OTP",
        'Accept-Version: V2',
        'Content-Type: application/json',
    ));
    
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload);

    $response = curl_exec($ch);

    if (curl_errno($ch)) {
        $error_msg = curl_error($ch);
        curl_close($ch);
        throw new Exception("cURL error: $error_msg");
    }

    curl_close($ch);

    return $response;
}

$config_data = array(
    'emailAddress' => 'test_email@gmail.com',
    'commonName' => 'mydomain.com',
    'country' => 'SA',
    'organizationalUnitName' => 'Demora Branch',
    'organizationName' => 'Test Company',
    'serialNumber' => '1-Model|2-3492842|3-49182743421',
    'vatNumber' => '317460736806263',
    'invoiceType' => '1100',
    'registeredAddress' => 'TestAddress',
    'businessCategory' => 'Software Development'
);

define('CONFIG_CNF_FILE_TEMPLATE', 
"oid_section=OIDS
[ OIDS ]
certificateTemplateName= 1.3.6.1.4.1.311.20.2

[req]
default_bits=2048
emailAddress=__emailAddress
req_extensions=v3_req
x509_extensions=v3_Ca
prompt=no
default_md=sha256
req_extensions=req_ext
distinguished_name=dn

[dn]
CN=__commonName
C=__country
OU=__organizationalUnitName
O=__organizationName

[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[req_ext]
certificateTemplateName = ASN1:PRINTABLESTRING:PREZATCA-code-Signing
subjectAltName = dirName:alt_names

[alt_names]
SN=__serialNumber
UID=__vatNumber
title=__invoiceType
registeredAddress=__registeredAddress
businessCategory=__businessCategory");


$template = CONFIG_CNF_FILE_TEMPLATE;

foreach ($config_data as $key => $value) {
    $configCnf = str_replace("__$key", $value, $template);
    $template = $configCnf;
}

echo $template;

$configPath = 'config.cnf';
$privateKeyFile = 'PrivateKey.pem';
$csrFile = 'taxpayer.csr';

file_put_contents($configPath, $template);

generateEcPrivateKey($privateKeyFile);

$csr = generateCsr($privateKeyFile, $configPath, $csrFile);

$OTP = "123456";
$url = "https://gw-fatoora.zatca.gov.sa/e-invoicing/developer-portal/compliance";

$response = complianceCSID($csr, $OTP, $url);

if (is_string($response) && is_array(json_decode($response, true)) && (json_last_error() == JSON_ERROR_NONE)) {
    echo "\n\ncomplianceCSID Server Response: \n" . json_encode(json_decode($response), JSON_PRETTY_PRINT);
} else {
    echo "\n\ncomplianceCSID Server Response: \n" . $response;
}

?>



1 Like

i’m also facing same issue,

Thank you this worked great! I was having a requirement where I am not allowed to execute any commands. But I think I will just run the commands and see the full process before trying to achieve it without using commands.

Thank you for your help. I appreciate you!

Dear,

Can you kindly elaborate more on the issue you are facing?

Have you successfully created CSR without OpenSSL Command in PHP?

I succeeded in Python code, for PHP code always fails to add extension [alt_names] to CSR.

Please share your code if you succeed in doing it in PHP.

Thanks

Just tried again and it worked

CsrGenerator.php

<?php

class CsrGenerator
{
    private $config;
    private $environment_type;
    private $asn_template;

    public function __construct($config, $environment_type)
    {
        $this->config = $config;
        $this->environment_type = $environment_type;
        $this->asn_template = $this->getAsnTemplate();
    }

    private function getAsnTemplate()
    {
        if ($this->environment_type == 'NonProduction') {
            return 'TSTZATCA-Code-Signing';
        } elseif ($this->environment_type == 'Simulation') {
            return 'PREZATCA-Code-Signing';
        } elseif ($this->environment_type == 'Production') {
            return 'ZATCA-Code-Signing';
        } else {
            throw new Exception("Invalid environment type specified.");
        }
    }

    // Membuat konfigurasi .cnf dinamis berdasarkan input
    private function generateConfigFile()
    {
        $config = $this->config;
        $cnfContent = "
oid_section = OIDs
[OIDs]
certificateTemplateName=1.3.6.1.4.1.1311.20.2

[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn

[dn]
CN={$config['csr.common.name']}
OU={$config['csr.organization.unit.name']}
O={$config['csr.organization.name']}
C={$config['csr.country.name']}

[req_ext]
certificateTemplateName = ASN1:PRINTABLESTRING:{$this->asn_template}
subjectAltName = dirName:alt_names

[alt_names]
SN={$config['csr.serial.number']}
UID={$config['csr.organization.identifier']}  # Default from your config
title={$config['csr.invoice.type']}  # Default from your config
registeredAddress={$config['csr.location.address']}  # Default from your config
businessCategory={$config['csr.industry.business.category']}
";


        // Menyimpan konten ke file config.cnf
        $filePath = 'config.cnf';
        file_put_contents($filePath, $cnfContent);

        return $filePath;
    }

    public function generatePrivateKey()
    {
        // Menggunakan SECP256K1 untuk private key
        $privateKey = openssl_pkey_new([
            'private_key_type' => OPENSSL_KEYTYPE_EC,
            'curve_name' => 'secp256k1', // Menggunakan SECP256K1 sesuai dengan permintaan
        ]);

        return $privateKey;
    }

    public function generateCsr()
    {
        // Menyusun file konfigurasi OpenSSL yang dinamis
        $configFile = $this->generateConfigFile();

        // Membuat private key
        $privateKey = $this->generatePrivateKey();

        // Membuat DN untuk CSR
        $dn = [
            "countryName" => $this->config['csr.country.name'] ?? 'SA',
            "organizationalUnitName" => $this->config['csr.organization.unit.name'] ?? '',
            "organizationName" => $this->config['csr.organization.name'] ?? '',
            "commonName" => $this->config['csr.common.name'] ?? '',
        ];

        // Pastikan untuk menyertakan file konfigurasi yang benar dalam csrConfig
        $csrConfig = [
            "config" => $configFile, // Menyertakan file konfigurasi yang telah dibuat
            "digest_alg" => "sha256",
        ];

        // Buat CSR menggunakan konfigurasi yang sudah diubah
        $csr = openssl_csr_new($dn, $privateKey, $csrConfig);

        if (!$csr) {
            throw new Exception('Error generating CSR: ' . openssl_error_string());
        }

        // Menandatangani CSR
        $csrPem = '';
        if (!openssl_csr_sign($csr, null, $privateKey, 365, ['digest_alg' => 'sha256'])) {
            throw new Exception('Error signing CSR: ' . openssl_error_string());
        }

        // Menyimpan private key dan CSR ke dalam format PEM
        openssl_pkey_export($privateKey, $privateKeyPem);
        openssl_csr_export($csr, $csrPem);

        // Strip header/footer dari private key
        $privateKeyContent = preg_replace('/-+BEGIN[^-]+-+|-+END[^-]+-+/', '', $privateKeyPem);  
        $privateKeyContent = str_replace(["\r", "\n"], '', $privateKeyContent); 

        // Encode CSR dalam Base64
        $csrBase64 = base64_encode($csrPem);

        return [$privateKeyContent, $csrBase64];
    }

    public function saveToFiles($privateKeyPem, $csrPem)
    {
        if (!file_exists('certificates')) {
            if (!mkdir('certificates', 0777, true)) {
                throw new Exception('Failed to create certificates directory.');
            }
        }

        file_put_contents('certificates/PrivateKey.pem', $privateKeyPem);
        file_put_contents('certificates/taxpayer.csr', $csrPem);

        echo "\nPrivate key and CSR have been saved to certificates/PrivateKey.pem and certificates/taxpayer.csr, respectively.\n";
    }
}

.
CsrGenerator_test.php

<?php
require("Helpers/CsrGenerator.php");

$config = [
    "csr.common.name" => "TST-886431145-399999999900003",
    "csr.serial.number" => "1-TST|2-TST|3-ed22f1d8-e6a2-1118-9b58-d9a8f11e445f",
    "csr.organization.identifier" => "399999999900003",
    "csr.organization.unit.name" => "Riyadh Branch",
    "csr.organization.name" => "Maximum Speed Tech Supply LTD",
    "csr.country.name" => "SA",
    "csr.invoice.type" => "1100",
    "csr.location.address" => "RRRD2929",
    "csr.industry.business.category" => "Supply activities"
];

$environmentType = "NonProduction";

// Instantiate CSR Generator
$csrGen = new CsrGenerator($config, $environmentType);
list($privateKeyContent, $csrBase64) = $csrGen->generateCsr();

echo "Private Key (without header and footer):\n";
echo $privateKeyContent . "\n\n";
echo "Base64 Encoded CSR:\n";
echo $csrBase64 . "\n\n";

// Define ZATCA endpoint and OTP
$otp = '123456';
$url = "https://gw-fatoora.zatca.gov.sa/e-invoicing/developer-portal/compliance";

// Prepare JSON payload
$jsonPayload = json_encode([
    'csr' => $csrBase64
]);

// Set cURL options
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'accept: application/json',
    'accept-language: en',
    'OTP: ' . $otp,
    'Accept-Version: V2',
    'Content-Type: application/json'
]);

// Execute cURL and get the response
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

curl_close($ch);

// Output server response
if ($httpCode === 200) {
    echo "\n\nServer Response: \n" . json_encode(json_decode($response), JSON_PRETTY_PRINT);
} else {
    echo "\n\nServer Response: \n" . $response;
    if ($response === false) {
        echo "cURL error: " . curl_error($ch);
    }
}
?>

.
Result


Private Key (without header and footer):
MHQCAQEEID9l9tzL6GHLkNR0JHFciG/PCrSTpq7OynUHwxrr50KxoAcGBSuBBAAKoUQDQgAE9wCiRXFWxtvlc92tKCNNpOHusjiyNXSIeBm+1fJuf5pbebgUVUL/Fc1/w2pwShbHjPKUDjyzJPI9A8qlryj7+Q==

Base64 Encoded CSR:
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ0dqQ0NBYjhDQVFBd2RURUxNQWtHQTFVRUJoTUNVMEV4RmpBVUJnTlZCQXNNRFZKcGVXRmthQ0JDY21GdQpZMmd4SmpBa0JnTlZCQW9NSFUxaGVHbHRkVzBnVTNCbFpXUWdWR1ZqYUNCVGRYQndiSGtnVEZSRU1TWXdKQVlEClZRUUREQjFVVTFRdE9EZzJORE14TVRRMUxUTTVPVGs1T1RrNU9Ua3dNREF3TXpCV01CQUdCeXFHU000OUFnRUcKQlN1QkJBQUtBMElBQlBjQW9rVnhWc2JiNVhQZHJTZ2pUYVRoN3JJNHNqVjBpSGdadnRYeWJuK2FXM200RkZWQwoveFhOZjhOcWNFb1d4NHp5bEE0OHN5VHlQUVBLcGE4bysvbWdnZW93Z2VjR0NTcUdTSWIzRFFFSkRqR0IyVENCCjFqQWtCZ2tyQmdFRUFZb2ZGQUlFRnhNVlZGTlVXa0ZVUTBFdFEyOWtaUzFUYVdkdWFXNW5NSUd0QmdOVkhSRUUKZ2FVd2dhS2tnWjh3Z1p3eE96QTVCZ05WQkFRTU1qRXRWRk5VZkRJdFZGTlVmRE10WldReU1tWXhaRGd0WlRaaApNaTB4TVRFNExUbGlOVGd0WkRsaE9HWXhNV1UwTkRWbU1SOHdIUVlLQ1pJbWlaUHlMR1FCQVF3UE16azVPVGs1Ck9UazVPVEF3TURBek1RMHdDd1lEVlFRTURBUXhNVEF3TVJFd0R3WURWUVFhREFoU1VsSkVNamt5T1RFYU1CZ0cKQTFVRUR3d1JVM1Z3Y0d4NUlHRmpkR2wyYVhScFpYTXdDZ1lJS29aSXpqMEVBd0lEU1FBd1JnSWhBS0ZqWHdrNgpKMFpKUkRQU3hjSjN0T0ZsaURvM1Bmb21WRWFER3IzMDNYcDhBaUVBOUoyTWc0NURhSEtnb3hLdUhQU1RoNkVQCkhhckM5bmRGemZiTGRDT0pzcnc9Ci0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo=

Server Response: 
{
    "requestID": 1234567890123,
    "dispositionMessage": "ISSUED",
    "binarySecurityToken": "TUlJQ1BUQ0NBZU9nQXdJQkFnSUdBWk1NWjB1SU1Bb0dDQ3FHU000OUJBTUNNQlV4RXpBUkJnTlZCQU1NQ21WSmJuWnZhV05wYm1jd0hoY05NalF4TVRBNE1UVXpNVFV4V2hjTk1qa3hNVEEzTWpFd01EQXdXakIxTVFzd0NRWURWUVFHRXdKVFFURVdNQlFHQTFVRUN3d05VbWw1WVdSb0lFSnlZVzVqYURFbU1DUUdBMVVFQ2d3ZFRXRjRhVzExYlNCVGNHVmxaQ0JVWldOb0lGTjFjSEJzZVNCTVZFUXhKakFrQmdOVkJBTU1IVlJUVkMwNE9EWTBNekV4TkRVdE16azVPVGs1T1RrNU9UQXdNREF6TUZZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUFvRFFnQUU5d0NpUlhGV3h0dmxjOTJ0S0NOTnBPSHVzaml5TlhTSWVCbSsxZkp1ZjVwYmViZ1VWVUwvRmMxL3cycHdTaGJIalBLVURqeXpKUEk5QThxbHJ5ajcrYU9Cd1RDQnZqQU1CZ05WSFJNQkFmOEVBakFBTUlHdEJnTlZIUkVFZ2FVd2dhS2tnWjh3Z1p3eE96QTVCZ05WQkFRTU1qRXRWRk5VZkRJdFZGTlVmRE10WldReU1tWXhaRGd0WlRaaE1pMHhNVEU0TFRsaU5UZ3RaRGxoT0dZeE1XVTBORFZtTVI4d0hRWUtDWkltaVpQeUxHUUJBUXdQTXprNU9UazVPVGs1T1RBd01EQXpNUTB3Q3dZRFZRUU1EQVF4TVRBd01SRXdEd1lEVlFRYURBaFNVbEpFTWpreU9URWFNQmdHQTFVRUR3d1JVM1Z3Y0d4NUlHRmpkR2wyYVhScFpYTXdDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBT2NhUk83aHNXM0FSZkpFK3J6UzErZExmY2dqOVBSTzNJd3V1RG1wZDc0V0FpQVdyRE5jQlZ0eDE3bytqN2o0alNMaDEvYnNJSHFNUWUwdVJvNC80MGFDRmc9PQ==",
    "secret": "fwHuYqxG\/h+YnAuB1oPX\/Wzw3I9YZPzSXS1mqPxwQ3U=",
    "errors": null
}

Hope this help

any update on this issue

@sameer ,
I have shared 2 php codes how to make csr that pass csid compliance, you can try it, and let us know the result

Thank you this worked! <3