استكشاف أخطاء OpenSSL: حل مشكلة unable to get local issuer certificate
عند استخدام OpenSSL لإجراء اتصالات آمنة (مثل تنفيذ طلبات الشبكة في بيئة PHP، أو تشغيل أوامر openssl
وcurl
، أو عند إنشاء اتصال SSL/TLS في تطبيقات Node.js أو Python)، غالبًا ما يواجه المطورون الخطأ 20:unable to get local issuer certificate
. تعتبر هذه المشكلة شائعة وهي متعلقة بكيفية تحقق OpenSSL من شهادة الطرف الآخر، وتُعد من القضايا المتكررة تاريخيًا.
لأسباب أمنية، يتطلب OpenSSL بشكل افتراضي معرفة مصادر شهادات الجذر الموثوقة (CA) بشكل واضح أثناء التحقق من سلسلة الشهادات. إذا لم يستطع OpenSSL إيجاد أو تحديد هذه الجذور الموثوقة، فلن يتمكن من التحقق من صحة شهادة الخادم ويُبلغ عن هذا الخطأ.
ستجد في هذا الدليل شرحًا تفصيليًا لأسباب ظهور الخطأ، بالإضافة إلى طرق فعّالة لحله ضمن بيئة ServBay، بما يشمل خطوات الإعداد لـ PHP، Python، Node.js وأمر curl
لتكوين مصادر ثقة OpenSSL.
رسالة الخطأ 20:unable to get local issuer certificate
وصف المشكلة
عندما يحاول OpenSSL التحقق من شهادة SSL/TLS لخادم بعيد، يقوم ببناء سلسلة شهادات تبدأ من شهادة الخادم وصولاً إلى سلطة التصديق الجذرية (CA) الموثوقة. إذا تعذّر على OpenSSL إيجاد أي من الشهادات الوسيطة أو شهادة الجذر الضرورية محليًا، أو إذا لم يكن هناك مسار صحيح أو ملف معرفة لمصدر الثقة (CAFile أو CAPath)، ستفشل عملية التحقق وتظهر رسالة الخطأ 20:unable to get local issuer certificate
.
ببساطة، يعني ذلك أن OpenSSL لا يعرف أي سلطة تصديق يثق بها، ولذلك لا يمكنه التأكد من هوية الخادم المستهدف.
إصدار OpenSSL ومسار شهادة CA في ServBay
تعد ServBay بيئة تطوير ويب محلية مدمجة، تتوفر بها حزمة OpenSSL مُسبقًا إلى جانب مجموعة شائعة من شهادات الجذر CA العامة، تسهيلاً لاستخدام المطورين. ويعتمد إصدار OpenSSL المستخدم على نوع معالج جهازك:
- إصدار Apple Silicon (رقائق سلسلة M) من ServBay: يستخدم إصدار OpenSSL 3.2.1.
- الإصدار المتوافق مع معالجات Intel: يستخدم إصدار OpenSSL 1.1.1u.
تتواجد ملفات الشهادات الجذرية (cacert.pem
) ودليل الشهادات (certs
) الخاصة بكل إصدار ضمن مسار تثبيت ServBay. على كل مطور تحديد المسار المطابق لإصدار ServBay الخاص بجهازه:
# ملف مجمع يحتوي على كل الشهادات الجذرية الموثوقة
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# مجلد يحتوي على ملفات شهادات منفصلة (عادةً الملف cacert.pem كافٍ، وبعض التطبيقات قد تتطلب capath)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
2
3
4
# ملف مجمع يحتوي على كل الشهادات الجذرية الموثوقة
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# مجلد يحتوي على ملفات شهادات منفصلة
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
2
3
4
جوهر حل خطأ unable to get local issuer certificate
هو تحديد مسار cafile
أو capath
لهذا المصدر في الأماكن التي يحتاج فيها OpenSSL له، ليعرف من أين يتحقق من شهادات CA الموثوقة.
أمثلة على الحلول
فيما يلي طرق وطرق تطبيقية لتعيين مصادر شهادات CA الخاصة بـ OpenSSL عبر أدوات ولغات برمجة مختلفة.
المثال 1: اختبار الاتصال باستخدام أمر openssl
إذا استخدمت مباشرة أمر openssl s_client
واختبرت الاتصال وظهرت لك الأخطاء:
openssl s_client -quiet -connect gmail.com:443
قد ترى رسالة خطأ مشابهة، تتضمن verify error:num=20:unable to get local issuer certificate
:
depth=2 C=US, O=Google Trust Services LLC, CN=GTS Root R1
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=1 C=US, O=Google Trust Services, CN=WR2
verify return:1
depth=0 CN=gmail.com
verify return:1
# ... معلومات أخرى عن الاتصال ...
2
3
4
5
6
7
8
طريقة الحل:
أضف وسيطة -CAfile
محددة مسار ملف شهادات الثقة الذي توفره ServBay:
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/3.2/cacert.pem
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
عند النجاح في الاتصال والتحقق من الشهادة، ستظهر القيمة verify return:1
ولن يظهر أي سطر فيه verify error:num=20
:
depth=2 C=US, O=Google Trust Services LLC, CN=GTS Root R1
verify return:1
depth=1 C=US, O=Google Trust Services, CN=WR2
verify return:1
depth=0 CN=gmail.com
verify return:1
# ... معلومات أخرى عن الاتصال ...
2
3
4
5
6
7
المثال 2: إعداد OpenSSL في PHP
العديد من ميزات الشبكة في PHP (مثل استدعاء HTTPS بواسطه file_get_contents
أو فتح اتصال بـstream_socket_client
أو إضافة cURL) تعمل في الخلفية باستخدام OpenSSL. يمكنك تعريف مصدر الشهادات سواء من خلال ملف إعدادات php.ini
أو ضبط ذلك سياقيًا داخل الكود.
الطريقة A: تعديل php.ini
(موصى به)
هذه الطريقة هي الأبسط لتطبيق تغييرات شاملة. افتح ملف php.ini
الخاص بإصدار PHP الذي تستخدمه (يمكنك الوصول له من خلال لوحة تحكم ServBay)، انتقل إلى قسم [openssl]
وأضف/عدل الأسطر التالية لتعيين متغيري openssl.cafile
وopenssl.capath
تبعًا لنوع معالجك:
[openssl]
; تحديد ملف الشهادات الجذرية الموثوق بها
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; تحديد مجلد شهادات CA (اختياري لكن موصى به)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
2
3
4
5
[openssl]
; تحديد ملف الشهادات الجذرية الموثوق بها
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; تحديد مجلد شهادات CA (اختياري لكن موصى به)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
2
3
4
5
بعد تعديل php.ini
، أعِد تشغيل خدمة PHP في ServBay (أو أعد تشغيل ServBay بالكامل) لتفعيل التعديلات.
الطريقة B: ضبط التكوين داخل الكود (للاتصال الحالي فقط)
إذا كنت لا ترغب في تعميم الإعداد على كل التطبيق، يمكنك تعيين الخيار مباشرة ضمن سياق الاتصال في الكود عبر stream_context_create
وتحديد خيار ssl
وcafile
:
<?php
// مثال: الاتصال بسيرفر SMTP يستخدم SSL/TLS
$server = 'ssl0.ovh.net';
$port = 465;
// اختر المسار الصحيح حسب نسخة ServBay الخاصة بك
// لـ Apple Silicon:
$caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// لـ Intel:
// $caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
$contextOptions = [
'ssl' => [
'verify_peer' => true, // تفعيل التحقق من شهادة الطرف الآخر
'verify_peer_name' => true, // التحقق من أن اسم المضيف في الشهادة مطابق
'allow_self_signed' => false, // عدم السماح بالشهادات المُوقعة ذاتيًا
'cafile' => $caCertFile, // تعيين ملف شهادات CA
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // اختياري - مجلد الشهادات
],
];
$context = stream_context_create($contextOptions);
// قم بإنشاء الاتصال باستخدام السياق المُعد
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // المهلة الزمنية للاتصال
STREAM_CLIENT_CONNECT,
$context // تمرير الإعدادات
);
if ($connection) {
echo "Connection established to $server:$port\n";
// مثال: إرسال أمر EHLO
fwrite($connection, "EHLO servbay.demo\r\n"); // استخدام دومين تجريبي خاص بـ ServBay
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "Failed to connect to $server:$port. Error: $errstr ($errno)\n";
}
?>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
المثال 3: إعداد OpenSSL في Python (وحدة ssl)
يُمكنك في Python الاستفادة من وحدة ssl
لتحديد الشهادات الموثوقة عند إنشاء سياق اتصال SSL/TLS.
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# اختر المسار الصحيح حسب نسخة ServBay
# لـ Apple Silicon:
ca_cert_file = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem'
# لـ Intel:
# ca_cert_file = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem'
# إنشاء سياق SSL افتراضي مع تعيين ملف CA
context = ssl.create_default_context(cafile=ca_cert_file)
# يمكن تعيين مجلد أيضاً: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
# إنشاء اتصال socket عادي
with socket.create_connection((server, port)) as sock:
# تغليف socket كاتصال SSL
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"SSL connection established. Negotiated Protocol: {ssock.version()}")
# مثال: إرسال أمر EHLO
ssock.sendall(b"EHLO servbay.demo\r\n") # استخدام دومين ServBay
while True:
data = ssock.recv(4096)
if not data:
break
print(data.decode())
except Exception as e:
print(f"Failed to connect or SSL error: {e}")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
المثال 4: إعداد OpenSSL في Node.js (وحدة tls)
تُستخدم وحدة tls
في Node.js لإنشاء اتصالات SSL/TLS. يُمكنك تمرير مسار الشهادة الموثوقة عبر الخاصية ca
عند إعداد خيارات الاتصال. أبسط طريقة: قراءة محتوى ملف cacert.pem
المقدم من ServBay.
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // مثال مع موقع موثوق
const port = 443;
// اختر المسار الصحيح حسب نسخة ServBay
// لـ Apple Silicon:
const caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// لـ Intel:
// const caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
const options = {
host: server,
port: port,
// قراءة محتوى شهادة CA
ca: fs.readFileSync(caCertFile),
// بشكل افتراضي يقوم Node.js بالتحقق من اسم الخادم (checkServerIdentity)،
// إذا تم تعيين CA بشكل صحيح وكانت الشهادة صالحة، من المفترض أن تتم العملية بنجاح.
// لا تقم بتعطيل checkServerIdentity إلا لأسباب محددة وتدرك تبعاتها الأمنية!
// checkServerIdentity: () => { return null; } // <-- لا تستخدم هذا السطر إلا للضرورة القصوى لأسباب أمنية!
};
const socket = tls.connect(options, () => {
console.log('SSL connection established');
// في حالة HTTPS، تحتاج لإرسال طلب HTTP – هنا المثال يُظهر فقط نجاح الاتصال
// socket.write('GET / HTTP/1.1\r\nHost: ' + server + '\r\n\r\n');
});
socket.on('data', (data) => {
console.log(data.toString());
});
socket.on('close', () => {
console.log('Connection closed');
});
socket.on('error', (error) => {
console.error('Error:', error.message); // طباعة رسالة الخطأ
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
تنبيه: تم إزالة الخيار checkServerIdentity: () => { return null; }
في مثال Node.js. تعطيل التحقق من اسم الخادم غير آمن للغاية. خطأ OpenSSL unable to get local issuer certificate
يتعلق بجذر الثقة، وليس بالتحقق من اسم الخادم (verify_peer_name
). عند تعيين الخيار ca
بشكل صحيح وضمان صلاحية الشهادة، سيتم التحقق من اسم الخادم تلقائيًا في Node.js. إذا ظهر خطأ في التحقق من اسم الخادم، فغالبًا المشكلة في الشهادة نفسها وليس بمصدر الثقة.
المثال 5: استخدام أوامر curl
مع OpenSSL
يستخدم أمر curl
غالبًا مكتبة OpenSSL أو مكتبة SSL أخرى عند إجراء الطلبات عبر HTTPS. يمكنك تحديد ملف شهادات CA مباشرة من خلال الخيار --cacert
.
# الوصول لموقع HTTPS باستخدام ملف شهادة CA من ServBay
# اختر المسار الصحيح حسب نوع الشريحة
# لـ Apple Silicon:
curl --cacert /Applications/ServBay/package/common/openssl/3.2/cacert.pem https://example.com
# لـ Intel:
# curl --cacert /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem https://example.com
2
3
4
5
6
7
إذا تم تحديد المسار بشكل صحيح وكانت شهادة الخادم صالحة، سيتمكن curl
من تنفيذ الطلب دون الإبلاغ عن أخطاء تحقق الشهادة.
الخلاصة
خطأ 20:unable to get local issuer certificate
شائع جدًا عند محاولة فتح اتصالات SSL/TLS باستخدام OpenSSL. السبب الرئيسي هو ضرورة تحديد مصدر ثقة واضح (Trust Store) يحتوي على شهادات الجذر الموثوقة للتحقق من شهادة الخادم.
توفر ServBay للمطورين ملف cacert.pem
مجمعًا يتضمن أغلب شهادات الجذر العامة المستخدمة عالمياً.
الحل هو ببساطة تعيين مسار ملف cacert.pem
ضمن إعدادات بيئة التطوير (سواء في php.ini
، إعدادات SSL البرمجية ضمن الكود، أو خيارات سطر الأوامر مثل -CAfile
مع openssl
أو --cacert
مع curl
). تأكد دومًا من تحديد المسار المطابق لنوع شريحة جهازك macOS (Apple Silicon أو Intel) وإصدار OpenSSL الموازي في ServBay.
بإعداد مصدر ثقة CA بشكل صحيح، تضمن أن بيئة التطوير المحلية لديك قادرة على التواصل بشكل آمن مع خدمات SSL/TLS الخارجية دون خسارة الأمان أو التعرض لأخطاء التوثيق.