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