> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# الشهادات الرقمية TLS للمبتدئين وصيغ ملفاتها
- URL: https://arabroot.io/articles/الشهادات-الرقمية-tls-و-x509-وصيغ-pem-و-der/
- Published: 2026-10-05T14:30:00.000Z
- Updated: 2026-10-14T02:12:06.000Z
- Description: كيف يعرف المتصفح أنه يتحدث مع الموقع الحقيقي، وماذا يوجد داخل الشهادة، ولماذا تأتي ملفاتها بأكثر من صيغة.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, الشبكات والبنية التحتية, الشبكات والوصول, الاستضافة الذاتية

عندما تفتح موقع البنك في المتصفح وترى القفل بجانب العنوان، فأنت تثق في أمرين معاً دون أن تفكر فيهما: أن البيانات بينك وبين السيرفر مشفرة فلا يقرؤها أحد في الطريق، وأن السيرفر الذي تتحدث معه هو سيرفر البنك فعلاً وليس جهازاً آخر ينتحل اسمه. والأمر الأول سهل نسبياً، أما الثاني فهو المشكلة الحقيقية، لأن التشفير مع الطرف الخطأ لا يحميك من شيء، والحل الذي يعتمد عليه الإنترنت كله لهذه المشكلة هو الشهادة الرقمية Digital Certificate. ومع الشهادة تأتي أسماء وملفات تحير أي مطور أو مدير سيرفرات في أول مرة: PEM وDER وCRT وCER وKEY وCSR وPFX وP12 وJKS، وكل برنامج يطلب صيغة مختلفة، وكل خطأ يظهر برسالة غامضة مثل `unable to get local issuer certificate` أو `no start line`.

وهذا المقال جزء من مرحلة الشبكات في خارطة الطريق، ويبدأ من المشكلة ثم يصل إلى الملفات والأوامر، وسوف نناقش في هذا المقال ما يلي:

- المشكلة: لماذا يفشل التشفير وحده، ولماذا يحتاج المفتاح العام إلى من يعرف به.
- المفاهيم: زوج المفاتيح والتوقيع والشهادة، وقراءة شهادة حقيقية حقلاً حقلاً بمعيار X.509.
- سلسلة الثقة من شهادة موقعك إلى الشهادة الرئيسية في نظام التشغيل، ولماذا يجب أن يرسل السيرفر الشهادة الوسيطة.
- أنواع الشهادات DV وOV وEV والشهادات الشاملة، ومدة الصلاحية التي تقصر إلى 47 يوماً، والإلغاء Revocation، وسجلات الشفافية Certificate Transparency.
- مصافحة TLS 1.3 باختصار، وأين يتم فحص الشهادة فيها.
- صيغ الملفات وامتداداتها: PEM وDER وPKCS#1 وPKCS#8 وPKCS#7 وPKCS#12 وJKS، وملفات Let's Encrypt الأربعة.
- تجربة عملية كاملة بمخرجات حقيقية: نبني جهة إصدار صغيرة، ونصدر شهادة، ونحولها بين الصيغ، ونشغل سيرفر TLS مع الشهادة الوسيطة ودونها.
- أين يوضع كل ملف في Nginx وHAProxy وApache وIIS وJava وKubernetes، ثم الأخطاء الشائعة وكيف تقرؤها، وجدول بالأوامر.

## التشفير وحده لا يحميك: مع من تتحدث؟

لنفرض أن المتصفح يريد أن يرسل كلمة المرور إلى `bank.example` دون أن يقرأها أحد في الطريق، فالحل الأول الذي يخطر على البال هو التشفير بالمفتاح العام Public Key Encryption، أي أن يرسل السيرفر مفتاحه العام، ويشفر المتصفح البيانات به، فلا يستطيع فكها إلا من يملك المفتاح الخاص المقابل، وهو السيرفر وحده. وهذا صحيح رياضياً، ولكن هناك مشكلة رئيسية في الخطوة الأولى: كيف يعرف المتصفح أن المفتاح العام الذي وصله هو مفتاح البنك فعلاً؟ فلو كان هناك مهاجم في منتصف الطريق Man in the Middle، في شبكة WiFi عامة مثلاً أو عند مزود خبيث أو عبر DNS مزور، فإنه يستقبل طلب المتصفح ويرد عليه بمفتاحه العام هو، فيشفر المتصفح كلمة المرور بمفتاح المهاجم ويرسلها إليه وهو مطمئن أن الاتصال مشفر، ثم يفكها المهاجم ويقرؤها ويعيد تشفيرها بمفتاح البنك الحقيقي ويرسلها إليه، فلا يلاحظ البنك ولا المستخدم أي شيء. وبالتالي فالتشفير هنا يعمل بشكل ممتاز، ولكنه يعمل مع الطرف الخطأ، وهذا معنى القاعدة التي يجب أن تبقى في ذهنك طوال المقال: **التشفير دون التحقق من الهوية لا قيمة له**.

وقد يتساءل البعض: لماذا لا يحفظ المتصفح المفتاح العام لكل موقع مسبقاً؟ والإجابة أن هذا ممكن لعدد صغير من المواقع، ولكن الإنترنت فيه مئات الملايين من النطاقات، والمفاتيح تتغير، والمواقع الجديدة تظهر كل دقيقة، فلا يمكن أن يحمل كل متصفح قائمة بكل مفاتيح العالم. والحل الثاني أن يسأل المتصفح السيرفر نفسه «هل أنت البنك؟»، وهذا لا يفيد لأن المهاجم سوف يقول نعم بكل تأكيد. والحل الصحيح هو ما نفعله في الحياة اليومية مع شخص لا نعرفه: نطلب منه وثيقة صادرة من جهة نثق بها نحن، مثل الهوية الوطنية أو جواز السفر، فنحن لا نعرف الشخص ولكننا نعرف الجهة التي أصدرت الوثيقة ونعرف كيف نتحقق من ختمها. وفي الإنترنت تسمى هذه الجهة جهة إصدار الشهادات Certificate Authority واختصاراً CA، والوثيقة هي الشهادة، وفيها اسم النطاق ومفتاحه العام، مختومة بتوقيع الجهة، والمتصفح يحمل مسبقاً قائمة صغيرة بالجهات التي يثق بها بدلاً من مفاتيح كل المواقع.

الصورة التالية تبين الفرق بين الحالتين:

![مخطط من جزأين، في الأعلى متصفح يرسل كلمة المرور مشفرة إلى مهاجم في منتصف الطريق أرسل له مفتاحه العام بدلاً من مفتاح البنك، فيفكها المهاجم ثم يمررها إلى سيرفر البنك، وفي الأسفل السيرفر يرسل شهادة موقعة من جهة إصدار يعرفها المتصفح، فيتحقق المتصفح من التوقيع والاسم ثم يشفر بالمفتاح الموجود في الشهادة، والمهاجم لا يستطيع أن يقدم شهادة صحيحة باسم البنك](https://arabroot.io/content/images/2026/10/certificates-01-problem.webp)

التشفير مع المهاجم يعمل بشكل ممتاز، ولذلك يجب أن نتحقق أولاً أن المفتاح العام يخص النطاق فعلاً

## المفتاح الخاص والمفتاح العام والتوقيع في دقيقة

الشهادة كلها مبنية على فكرة واحدة شرحناها بالتفصيل في قسم [التوقيع الرقمي بلغة بسيطة](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-spf-%D9%88-dkim-%D9%88-dmarc-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#digital-signature) في مقال SPF وDKIM وDMARC، ونلخصها هنا فقط بالقدر الذي نحتاجه:

- **زوج المفاتيح Key Pair**: مفتاحان مرتبطان رياضياً بخوارزمية مثل RSA أو ECDSA، المفتاح الخاص Private Key يبقى سراً على السيرفر ولا يغادره، والمفتاح العام Public Key ينشر لكل العالم، ولا يمكن استنتاج المفتاح الخاص من المفتاح العام.
- **التوقيع الرقمي Digital Signature**: يحسب صاحب المفتاح الخاص هاش Hash البيانات ثم يوقعه بمفتاحه الخاص، وأي شخص يملك المفتاح العام يستطيع أن يتحقق أن التوقيع صحيح وأن البيانات لم تتغير ولو بحرف واحد، ولكنه لا يستطيع أن ينتج توقيعاً جديداً.

وهذه الفكرة نفسها تراها في [Passkeys](https://arabroot.io/articles/%D8%A7%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D8%AF%D9%88%D9%86-%D9%83%D9%84%D9%85%D8%A9-%D9%85%D8%B1%D9%88%D8%B1-passkeys-%D9%88-fido2/#the-idea) حيث يوقع جهازك قيمة عشوائية Challenge يرسلها الموقع، وفي [مفاتيح JWKS](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-oauth-%D9%88-openid-connect-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#jwks) التي يتحقق بها التطبيق من توقيع ID Token. والفرق المهم أن مفتاح DKIM ومفاتيح JWKS مفاتيح عامة «عارية» تنشر في DNS أو في رابط على موقع المزود، ويثق بها الطرف الآخر لأنه يجلبها من مصدر يعرفه، أما الشهادة فهي مفتاح عام ومعه توقيع جهة ثالثة تشهد أن هذا المفتاح يخص هذا الاسم.

## ما هي الشهادة الرقمية؟

الشهادة الرقمية Digital Certificate هي ملف صغير يربط بين هوية، مثل اسم النطاق `github.com`، وبين مفتاح عام، مع مدة صلاحية، وكل ذلك موقع بالمفتاح الخاص لجهة إصدار Certificate Authority. والتشبيه الأقرب هو بطاقة الهوية: فيها اسمك وصورتك وتاريخ الإصدار والانتهاء، وعليها ختم الجهة التي أصدرتها، والموظف الذي يفحصها لا يعرفك شخصياً، وإنما يعرف شكل الختم ويثق في الجهة. والصورة في البطاقة تقابل المفتاح العام في الشهادة، فالبطاقة وحدها لا تثبت أنك صاحبها، وإنما مطابقة الصورة لوجهك، وكذلك الشهادة وحدها ليست سراً، فهي ترسل لكل زائر، والذي يثبت أن السيرفر صاحبها هو أنه يملك المفتاح الخاص المقابل للمفتاح العام فيها، كما سنرى في [مصافحة TLS](#tls-handshake).

وصيغة الشهادات المستخدمة في TLS والبريد وتوقيع الكود هي X.509 النسخة الثالثة، وقد عرفها الاتحاد الدولي للاتصالات ITU، والملف المرجعي لها في الإنترنت هو [RFC 5280](https://datatracker.ietf.org/doc/html/rfc5280?ref=arabroot.io). الصورة التالية تبين أجزاء الشهادة:

![بطاقة شهادة مرسومة فيها الحقول Subject وSAN وIssuer وValidity وSerial وPublic Key وKey Usage، وفي أسفلها ختم أحمر يمثل توقيع جهة الإصدار، وسهم من جهة الإصدار ومفتاحها الخاص إلى الختم، وملاحظة أن التوقيع يغطي كل الحقول فإذا تغير حرف واحد بطلت الشهادة](https://arabroot.io/content/images/2026/10/certificates-02-anatomy.webp)

الشهادة بيانات عامة يغطيها توقيع جهة الإصدار، فلا أحد يستطيع أن يغير الاسم أو المفتاح دون أن يبطل التوقيع

### نقرأ شهادة حقيقية: github.com

بدلاً من الشرح النظري سوف نجلب شهادة موقع معروف ونقرؤها حقلاً حقلاً. الأمر `openssl s_client` يفتح اتصال TLS ويطبع الشهادات التي يرسلها السيرفر، ثم نمرر الشهادة الأولى إلى `openssl x509` فيحفظها في ملف ويعرضها بشكل مقروء:

```bash
echo | openssl s_client -connect github.com:443 -servername github.com 2>/dev/null | openssl x509 -out github.pem
openssl x509 -in github.pem -text -noout
```

والمخرج سوف يكون كما يلي:

```text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            a5:9e:bd:b5:96:75:1d:b7:f5:c0:95:07:96:13:95:3c
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C=GB, O=Sectigo Limited, CN=Sectigo Public Server Authentication CA DV E36
        Validity
            Not Before: Sep  1 00:00:00 2026 GMT
            Not After : Nov 29 23:59:59 2026 GMT
        Subject: CN=github.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:85:36:1b:34:bc:b3:51:f7:20:e9:aa:9a:cb:e8:
                    27:2d:60:d1:31:7b:1d:38:d1:d8:c7:d7:a0:fa:5a:
                    b1:f1:2f:28:e7:99:51:46:61:22:38:b2:3e:b0:2b:
                    75:75:ec:00:e2:69:a6:cf:13:4e:2f:42:4e:fa:76:
                    35:b4:0d:3f:2a
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Authority Key Identifier:
                17:99:A8:04:C1:6F:E4:2D:70:A8:0A:10:3D:03:D3:E9:1A:B8:26:63
            X509v3 Subject Key Identifier:
                66:98:EC:4C:11:35:F7:4B:50:84:8B:A8:1C:36:65:D0:17:56:D4:E0
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            X509v3 Certificate Policies:
                Policy: 1.3.6.1.4.1.6449.1.2.2.7
                  CPS: https://sectigo.com/CPS
                Policy: 2.23.140.1.2.1
            Authority Information Access:
                CA Issuers - URI:http://crt.sectigo.com/SectigoPublicServerAuthenticationCADVE36.crt
                OCSP - URI:http://ocsp.sectigo.com
            CT Precertificate SCTs:
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : D7:6D:7D:10:D1:A7:F5:77:C2:C7:E9:5F:D7:00:BF:F9:
                                82:C9:33:5A:65:E1:D0:B3:01:73:17:C0:C8:C5:69:77
                    Timestamp : Sep  1 00:10:32.017 2026 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:45:02:20:7A:BB:A0:82:C9:0B:1A:A7:EA:66:4E:EC:
                                FB:01:BF:53:1C:F2:D6:56:D4:11:3D:3A:92:36:E0:BC:
                                65:63:BA:79:02:21:00:B3:37:AC:9A:5D:BB:45:D5:72:
                                F4:96:DB:DF:39:46:33:83:87:EF:41:05:1E:67:F1:F0:
                                79:94:BE:CB:39:79:E6
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : C8:A3:C4:7F:C7:B3:AD:B9:35:6B:01:3F:6A:7A:12:6D:
                                E3:3A:4E:43:A5:C6:46:F9:97:AD:39:75:99:1D:CF:9A
                    Timestamp : Sep  1 00:10:31.981 2026 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:45:02:20:70:B1:0B:F0:7D:54:AE:11:C1:AA:2A:9E:
                                25:37:F8:D6:3D:45:2E:B9:D5:17:F9:2B:78:7C:89:95:
                                0F:10:8A:99:02:21:00:D1:C9:BD:67:73:B1:71:FF:03:
                                DB:82:AD:2C:F5:B1:0E:24:DF:2A:30:4D:8C:6D:56:D7:
                                D3:25:CF:75:A3:37:B6
            X509v3 Subject Alternative Name:
                DNS:github.com, DNS:www.github.com
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:44:02:20:17:41:57:b6:3f:e6:dd:a8:f7:cf:2f:30:10:c6:
        2c:91:25:2d:60:0b:cc:f2:64:b7:77:69:86:6b:19:93:d0:84:
        02:20:09:ab:16:bf:d5:cf:09:61:85:87:b0:6c:b5:ad:03:54:
        6f:77:df:b8:22:54:ee:99:e3:8e:a2:5f:76:28:a6:85
```

في المخرج أعلاه لاحظ التالي:

- **الرقم التسلسلي Serial Number**: رقم فريد تعطيه جهة الإصدار لكل شهادة، وهو الذي يذكر في قوائم الإلغاء عندما تلغى الشهادة.
- **خوارزمية التوقيع Signature Algorithm**: الجهة وقعت هذه الشهادة بـ ECDSA مع SHA-256، والتوقيع نفسه في آخر المخرج تحت `Signature Value`.
- **الجهة المصدرة Issuer**: من وقع الشهادة، وهنا شهادة وسيطة لشركة Sectigo اسمها `Sectigo Public Server Authentication CA DV E36`، وليست الشهادة الرئيسية، كما سنرى في [سلسلة الثقة](#chain-of-trust).
- **الصلاحية Validity**: من 1 سبتمبر إلى 29 نوفمبر 2026، أي 90 يوماً تقريباً، وبعدها يرفضها كل متصفح حتى لو كان كل شيء آخر صحيحاً، ولاحظ أن الوقت بتوقيت UTC.
- **صاحب الشهادة Subject**: `CN=github.com`، والحقل CN أو Common Name بقي لأسباب تاريخية، والمتصفحات اليوم لا تعتمد عليه وإنما على الحقل SAN.
- **المفتاح العام Subject Public Key Info**: مفتاح ECDSA على المنحنى P-256، وهو المفتاح الذي سوف يتحقق به المتصفح أن السيرفر يملك المفتاح الخاص.
- **استخدام المفتاح Key Usage وExtended Key Usage**: هذا المفتاح للتوقيع فقط، وهذه الشهادة لسيرفر TLS فقط `TLS Web Server Authentication`، فلا تصلح لتوقيع الكود ولا لتوقيع البريد.
- **القيود الأساسية Basic Constraints**: `CA:FALSE` يعني أن صاحب هذه الشهادة لا يحق له أن يصدر شهادات لغيره، وهذا ما يمنع أي موقع من أن يستخدم شهادته لتوقيع شهادة باسم موقع آخر.
- **السياسات Certificate Policies**: الرقم `2.23.140.1.2.1` معناه أن الشهادة من نوع DV كما في [المتطلبات الأساسية Baseline Requirements](https://github.com/cabforum/servercert/blob/main/docs/BR.md?ref=arabroot.io) لمنتدى CA/Browser Forum، أي أن الجهة تحققت من التحكم في النطاق فقط.
- **معلومات الجهة Authority Information Access**: رابط لتنزيل الشهادة الوسيطة `CA Issuers`، ورابط خدمة OCSP لفحص الإلغاء.
- **CT Precertificate SCTs**: إيصالان من سجلين عامين للشفافية يثبتان أن الشهادة نشرت علناً قبل إصدارها، وسوف نشرح السبب في قسم [سجلات الشفافية](#transparency).
- **الأسماء البديلة Subject Alternative Name**: الأسماء التي تصلح لها الشهادة فعلاً، `github.com` و`www.github.com`، فإذا فتحت `api.github.com` فلا بد أن يرسل السيرفر شهادة أخرى فيها هذا الاسم.

## سلسلة الثقة: من شهادة موقعك إلى الشهادة الرئيسية

لاحظ أن شهادة GitHub لم توقعها الجهة الرئيسية لشركة Sectigo مباشرة، وإنما شهادة وسيطة Intermediate Certificate، والسبب أن المفتاح الخاص للشهادة الرئيسية Root Certificate هو أثمن ما تملكه الجهة، فلو سرق لأصبحت كل الشهادات التي تحته بلا قيمة، ولذلك تحفظه الجهات في أجهزة HSM غير متصلة بالشبكة، ولا تستخدمه إلا نادراً لتوقيع شهادات وسيطة، والشهادات الوسيطة هي التي توقع شهادات المواقع كل يوم. وإذا اخترقت شهادة وسيطة أمكن إلغاؤها وإصدار أخرى دون أن تتغير الشهادة الرئيسية الموجودة في ملايين الأجهزة. وبهذا تتكون سلسلة الثقة Chain of Trust:

1. **شهادة النطاق Leaf Certificate**: شهادة موقعك، موقعة من الشهادة الوسيطة، و`CA:FALSE`.
2. **الشهادة الوسيطة Intermediate Certificate**: موقعة من الشهادة الرئيسية، و`CA:TRUE`، وقد تكون هناك أكثر من شهادة وسيطة.
3. **الشهادة الرئيسية Root Certificate**: موقعة من نفسها Self-Signed، أي أن Issuer هو نفسه Subject، ولا يمكن التحقق منها بتوقيع، ولذلك لا تأتي ثقتها من السلسلة وإنما من وجودها مسبقاً في مخزن الثقة Trust Store على جهازك.

ومخزن الثقة هو قائمة الشهادات الرئيسية التي يثق بها نظام التشغيل أو المتصفح، وتديرها برامج رسمية لها شروط تدقيق صارمة، مثل [برنامج Mozilla](https://wiki.mozilla.org/CA?ref=arabroot.io) الذي تعتمد عليه Firefox وأغلب توزيعات Linux عبر الحزمة `ca-certificates`، و[برنامج Chrome Root Program](https://www.chromium.org/Home/chromium-security/root-ca-policy/?ref=arabroot.io)، إضافة إلى مخزن Windows ومخزن Apple. وفي Debian 13 الذي استخدمناه في التجربة يحتوي الملف `/etc/ssl/certs/ca-certificates.crt` على 150 شهادة رئيسية فقط، وهذه هي القائمة الصغيرة التي تغني المتصفح عن معرفة مفاتيح كل مواقع العالم. وعند الاتصال يبني الكلاينت السلسلة من شهادة النطاق صعوداً، ويتحقق من كل توقيع بالمفتاح العام للشهادة التي فوقه، حتى يصل إلى شهادة رئيسية موجودة في مخزنه، فإذا لم يصل رفض الاتصال.

الصورة التالية تبين السلسلة ومكان كل جزء منها:

![ثلاث بطاقات شهادات متتالية: شهادة النطاق github.com موقعة من الشهادة الوسيطة Sectigo DV E36، والوسيطة موقعة من الشهادة الرئيسية، والشهادة الرئيسية موجودة داخل مخزن الثقة في نظام التشغيل والمتصفح، والسيرفر يرسل الشهادتين الأوليين معاً، والكلاينت يتحقق من التوقيعات صعوداً بخطوات مرقمة](https://arabroot.io/content/images/2026/10/certificates-03-chain.webp)

السيرفر يرسل شهادته والشهادة الوسيطة، والشهادة الرئيسية موجودة مسبقاً في جهاز الزائر

ويمكنك أن ترى السلسلة التي يرسلها GitHub فعلاً بالخيار `-showcerts`، أو من أول مخرج `s_client`:

```bash
echo | openssl s_client -connect github.com:443 -servername github.com 2>&1 | head -18
```

والمخرج سوف يكون كما يلي:

```text
Connecting to 20.233.83.145
depth=2 C=GB, O=Sectigo Limited, CN=Sectigo Public Server Authentication Root E46
verify return:1
depth=1 C=GB, O=Sectigo Limited, CN=Sectigo Public Server Authentication CA DV E36
verify return:1
depth=0 CN=github.com
verify return:1
CONNECTED(00000003)
---
Certificate chain
 0 s:CN=github.com
   i:C=GB, O=Sectigo Limited, CN=Sectigo Public Server Authentication CA DV E36
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256
   v:NotBefore: Sep  1 00:00:00 2026 GMT; NotAfter: Nov 29 23:59:59 2026 GMT
 1 s:C=GB, O=Sectigo Limited, CN=Sectigo Public Server Authentication CA DV E36
   i:C=GB, O=Sectigo Limited, CN=Sectigo Public Server Authentication Root E46
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA384
   v:NotBefore: Mar 22 00:00:00 2021 GMT; NotAfter: Mar 21 23:59:59 2036 GMT
```

في المخرج أعلاه لاحظ التالي:

- السطور `depth` هي ترتيب التحقق، فالعمق 0 شهادة النطاق، والعمق 1 الوسيطة، والعمق 2 الشهادة الرئيسية التي وجدها OpenSSL في مخزن النظام، و`verify return:1` يعني أن كل مستوى نجح.
- في `Certificate chain` السطر `s:` هو Subject والسطر `i:` هو Issuer، وكل Issuer يجب أن يساوي Subject الشهادة التي تليه، وهكذا تترابط السلسلة.
- الشهادة الوسيطة صالحة حتى 2036، بينما شهادة النطاق 90 يوماً فقط، وهذا الفرق مقصود: الشهادات العليا قليلة ومحمية، وشهادات المواقع كثيرة وتتغير باستمرار.

ولو استعرضت المخرج كاملاً لوجدت أن GitHub يرسل شهادة ثالثة أيضاً، فيها `Sectigo Public Server Authentication Root E46` موقعة من جهة أقدم اسمها `USERTrust ECC Certification Authority`، وهذا ما يسمى التوقيع المتقاطع Cross-Signing، والفكرة أن الشهادة الرئيسية الجديدة قد لا تكون موجودة بعد في مخازن الأجهزة القديمة، فتوقعها جهة قديمة موجودة فيها، فيستطيع الجهاز القديم أن يكمل السلسلة إلى الجهة القديمة والجهاز الحديث يتوقف عند الجديدة.

### لماذا يجب أن يرسل السيرفر الشهادة الوسيطة؟

الشهادات الرئيسية موجودة في جهاز الزائر، أما الشهادات الوسيطة فهي كثيرة وتتغير، ولذلك تطلب [مواصفة TLS 1.3](https://datatracker.ietf.org/doc/html/rfc8446?ref=arabroot.io#section-4.4.2) من السيرفر أن يرسل شهادته ومعها الشهادات التي تربطها بالشهادة الرئيسية. فإذا أعددت السيرفر بملف شهادة النطاق وحده فسوف يعمل الموقع في بعض المتصفحات ويفشل في غيرها، والسبب أن Firefox مثلاً [ينزل كل الشهادات الوسيطة المعروفة مسبقاً](https://blog.mozilla.org/security/2020/11/13/preloading-intermediate-ca-certificates-into-firefox/?ref=arabroot.io) لأن هذا الخطأ من أكثر أخطاء إعداد السيرفرات شيوعاً، وبعض المتصفحات تجلب الشهادة الناقصة من الرابط `CA Issuers` الذي رأيناه في الشهادة، بينما `curl` وPython وJava وتطبيقات الجوال وأغلب المكتبات لا تفعل ذلك، فتظهر الرسالة `unable to get local issuer certificate`. وهذا الفرق هو سبب الجملة المشهورة: «الموقع يعمل في المتصفح ولكن التطبيق يرفضه»، وسوف نرى الخطأ بأنفسنا في [التجربة العملية](#lab-tls-server)، والحل دائماً أن يقرأ السيرفر ملف السلسلة الكامل `fullchain.pem` وليس `cert.pem`.

## أنواع الشهادات: DV وOV وEV والشهادات الشاملة

تختلف الشهادات في مقدار ما تحققت منه الجهة قبل إصدارها، وليس في قوة التشفير، فالتشفير واحد في الأنواع الثلاثة:

- **التحقق من النطاق Domain Validation واختصاراً DV**: تتحقق الجهة فقط أنك تتحكم في النطاق، بملف على الموقع أو بسجل في DNS عبر بروتوكول ACME، وهذا ما تصدره Let's Encrypt مجاناً وتلقائياً، وقد شرحنا طرق التحقق في دليل [Nginx Proxy Manager](https://arabroot.io/articles/nginx-proxy-manager-%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-tls/#ssl).
- **التحقق من المنظمة Organization Validation واختصاراً OV**: تتحقق الجهة إضافة إلى ذلك من وجود المنظمة قانونياً، ويظهر اسمها في الحقل `O=` داخل Subject.
- **التحقق الموسع Extended Validation واختصاراً EV**: تحقق أشد بخطوات موثقة، وكانت المتصفحات تعرض اسم الشركة باللون الأخضر بجانب العنوان، ولكن Chrome [نقل هذا العرض منذ الإصدار 77](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/ev-to-page-info.md?ref=arabroot.io) إلى نافذة معلومات الصفحة بعد أن تبين أن المستخدمين لا يلاحظونه، ولذلك لم يعد لـ EV أثر يراه الزائر في أغلب الحالات.

أما من حيث الأسماء التي تغطيها الشهادة فهناك نوعان قد يجتمعان في شهادة واحدة:

- **شهادة متعددة الأسماء SAN Certificate**: فيها عدة أسماء في الحقل SAN، مثل `github.com` و`www.github.com` كما رأينا.
- **الشهادة الشاملة Wildcard Certificate**: فيها اسم مثل `*.example.com` يغطي كل نطاق فرعي من مستوى واحد، ولا يغطي `example.com` نفسه ولا `a.b.example.com`، ولا تصدرها Let's Encrypt إلا بالتحقق عبر DNS، وتفاصيلها في قسم [الشهادة الشاملة](https://arabroot.io/articles/nginx-proxy-manager-%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-tls/#wildcard-certificate).

ومتى تختار شهادة لكل اسم أو شهادة SAN واحدة أو شهادة شاملة، وكيف يختار السيرفر الشهادة بإشارة SNI، شرحناه بتجربة عملية في مقال [سيرفر واحد ونطاقات كثيرة](https://arabroot.io/articles/%D8%B3%D9%8A%D8%B1%D9%81%D8%B1-%D9%88%D8%A7%D8%AD%D8%AF-%D9%88%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%83%D8%AB%D9%8A%D8%B1%D8%A9-sni-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A9-san/).

## لماذا تقصر مدة الشهادات كل عام؟

كلما طالت مدة الشهادة طالت الفترة التي يستطيع فيها من سرق المفتاح الخاص أن ينتحل الموقع، وطالت الفترة التي تبقى فيها معلومات قديمة مثل نطاق بيع لشخص آخر صحيحة في شهادة صادرة، والإلغاء كما سنرى في القسم التالي لا يعمل بشكل موثوق، ولذلك فالحل العملي الوحيد هو أن تنتهي الشهادة بسرعة. وقد وافق منتدى CA/Browser Forum، الذي يجمع جهات الإصدار والمتصفحات، في 11 أبريل 2025 على [القرار SC-081v3](https://cabforum.org/2025/04/11/ballot-sc081v3-introduce-schedule-of-reducing-validity-and-data-reuse-periods/?ref=arabroot.io) بأغلبية 25 جهة إصدار دون أي اعتراض، وبموافقة Apple وGoogle وMicrosoft وMozilla، وأصبح الجدول التالي جزءاً من [المتطلبات الأساسية](https://github.com/cabforum/servercert/blob/main/docs/BR.md?ref=arabroot.io):

| الشهادات الصادرة | أقصى مدة للشهادة | أقصى مدة لإعادة استخدام التحقق من النطاق |
| ---------------- | ---------------- | ---------------------------------------- |
| قبل 15 مارس 2026 | 398 يوماً        | 398 يوماً                                |
| من 15 مارس 2026  | 200 يوم          | 200 يوم                                  |
| من 15 مارس 2027  | 100 يوم          | 100 يوم                                  |
| من 15 مارس 2029  | 47 يوماً         | 10 أيام                                  |

وLet's Encrypt تسبق هذا الجدول، فشهاداتها 90 يوماً منذ البداية، وقد [أعلنت في ديسمبر 2025](https://letsencrypt.org/2025/12/02/from-90-to-45/?ref=arabroot.io) أنها سوف تنتقل في الملف الافتراضي إلى 64 يوماً في 10 فبراير 2027 ثم إلى 45 يوماً في 16 فبراير 2028، وتنصح بالتجديد عند ثلثي مدة الشهادة تقريباً، أو باستخدام خدمة ARI التي تخبر برنامج ACME بموعد التجديد المناسب. والنتيجة العملية أن التجديد اليدوي للشهادات أصبح غير ممكن، فشهادة كل 47 يوماً لعشرين نطاقاً تعني أنك سوف تجدد شهادة كل يومين ونصف تقريباً، ولذلك يجب أن يكون التجديد آلياً بالكامل عبر ACME، كما تفعل [Nginx Proxy Manager](https://arabroot.io/articles/nginx-proxy-manager-%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-tls/) و[Traefik](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-traefik-reverse-proxy/) و[Caddy](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-caddy-reverse-proxy/)، ومع مراقبة تنبهك إذا فشل التجديد.

## إلغاء الشهادة قبل موعدها: CRL وOCSP

إذا سرق المفتاح الخاص لموقعك فيجب أن تلغي Revoke الشهادة فوراً، ولكن كيف يعرف كل متصفح في العالم أن شهادة ما زالت صالحة التاريخ أصبحت ملغاة؟ هناك طريقتان تاريخياً:

- **قائمة الشهادات الملغاة Certificate Revocation List واختصاراً CRL**: ملف تنشره الجهة دورياً فيه الأرقام التسلسلية للشهادات الملغاة، والكلاينت ينزله ويبحث فيه.
- **بروتوكول OCSP**: يسأل الكلاينت الجهة عن شهادة واحدة برقمها التسلسلي ويحصل على جواب موقع: صالحة أو ملغاة، كما في [RFC 6960](https://datatracker.ietf.org/doc/html/rfc6960?ref=arabroot.io).

ويبدو OCSP الحل الأنسب لأنه سريع وخاص بشهادة واحدة، ولكن له مشكلتين، الأولى أن الجهة تعرف من سؤال الكلاينت أي موقع يزوره هذا العنوان IP ومتى، والثانية أن المتصفحات إذا لم تصل إلى خدمة OCSP لا ترفض الاتصال عادة، وإلا لتعطل نصف الإنترنت مع كل عطل عند الجهة، وهذا يعني أن المهاجم الذي يحجب OCSP يعطل الفحص كله. ولذلك [أعلنت Let's Encrypt في ديسمبر 2024](https://letsencrypt.org/2024/12/05/ending-ocsp/?ref=arabroot.io) إنهاء OCSP والاعتماد على CRL فقط، وذكرت السبب صراحة: الخطر على الخصوصية، فحذفت روابط OCSP من شهاداتها في 7 مايو 2025، وأوقفت خدماتها في 6 أغسطس 2025\. فإذا قرأت شهادة صادرة من Let's Encrypt اليوم فسوف تجد فيها `CRL Distribution Points` بدلاً من رابط OCSP، وإذا كان في إعداد Nginx القديم `ssl_stapling on` لهذه الشهادات فلم يعد له عمل. والمتصفحات نفسها تتجه إلى قوائم مجمعة تصلها مع التحديثات بدلاً من سؤال الجهة مع كل زيارة، مثل [CRLite في Firefox](https://blog.mozilla.org/security/2020/01/09/crlite-part-1-all-web-pki-revocations-compressed/?ref=arabroot.io)، وهذا سبب آخر لتقصير مدة الشهادات، فالشهادة القصيرة تنتهي قبل أن تحتاج إلى إلغاء في أغلب الحالات.

## سجلات الشفافية Certificate Transparency وسجل CAA

كل ما سبق يفترض أن جهات الإصدار نفسها لا تخطئ، ولكن ماذا لو أصدرت جهة شهادة باسم نطاقك لشخص آخر، بخطأ أو لأنها اخترقت؟ فالمتصفح سوف يقبلها لأن توقيعها صحيح، وأنت لن تعرف بوجودها أصلاً. والحل هو سجلات الشفافية Certificate Transparency واختصاراً CT، المعرفة في [RFC 6962](https://datatracker.ietf.org/doc/html/rfc6962?ref=arabroot.io)، وهي سجلات عامة لا يمكن الحذف منها ولا التعديل فيها، تشغلها جهات مستقلة، وكل شهادة عامة يجب أن تسجل فيها قبل أن تصدر، والسجل يعطي الجهة إيصالاً موقعاً اسمه SCT، وهو ما رأيناه مرتين في شهادة GitHub. ومتصفح Chrome [يرفض أي شهادة عامة](https://googlechrome.github.io/CertificateTransparency/ct%5Fpolicy.html?ref=arabroot.io) لا تحمل العدد المطلوب من هذه الإيصالات. والنتيجة أن أي شهادة تصدر باسم نطاقك تظهر علناً، وتستطيع أن تبحث عنها في موقع [crt.sh](https://crt.sh/?ref=arabroot.io) بكتابة اسم نطاقك، أو بالرابط `https://crt.sh/?q=example.com`، فإذا وجدت شهادة لم تطلبها فهذا إنذار مبكر بأن هناك من يحاول انتحال موقعك. ولاحظ أن هذا يعني أيضاً أن أسماء نطاقاتك الفرعية مثل `staging-old.example.com` منشورة للجميع بمجرد أن تصدر لها شهادة عامة، فلا تعتمد على إخفاء الاسم كوسيلة حماية.

📌

في 29 أغسطس 2011 ظهرت شهادة مزورة لـ `*.google.com` صادرة من جهة الإصدار الهولندية DigiNotar، واستخدمت في هجمات اعتراض على مستخدمي Gmail في إيران، وقد [كشفها Chrome](https://security.googleblog.com/2011/08/update-on-attempted-man-in-middle.html?ref=arabroot.io) لأنه كان يحمل قائمة بالجهات المسموح لها بإصدار شهادات Google، ثم [أزالت Mozilla ثقتها](https://blog.mozilla.org/security/2011/08/29/fraudulent-google-com-certificate/?ref=arabroot.io) في الشهادة الرئيسية للجهة بالكامل، وتبين أن DigiNotar اكتشفت الاختراق في 19 يوليو ولم تحدد كل الشهادات التي أصدرها المخترق، فأعلنت [إفلاسها في 20 سبتمبر 2011](https://www.helpnetsecurity.com/2011/09/20/diginotar-files-for-bankruptcy/?ref=arabroot.io). والدرس أن الثقة في أي جهة إصدار يجب أن تكون قابلة للمراقبة، ومن حوادث مثل هذه جاءت سجلات الشفافية.

والأداة الثانية في يدك هي سجل CAA في DNS، فهو يحدد الجهات المسموح لها بإصدار شهادات لنطاقك، ويجب على كل جهة أن تفحصه قبل الإصدار، فإذا كان سجلك يسمح لـ `letsencrypt.org` وحدها فسوف ترفض أي جهة أخرى أن تصدر شهادة باسمك، وقد شرحنا السجل بمثال حقيقي في قسم [سجل CAA](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-dns-%D9%88%D8%B3%D8%AC%D9%84%D8%A7%D8%AA%D9%87-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#caa). وبهذا يكون لديك منع مسبق بسجل CAA، ومراقبة لاحقة بسجلات الشفافية.

## أين تفحص الشهادة في مصافحة TLS 1.3؟

الشهادة لا تشفر البيانات بنفسها، وإنما تستخدم مرة واحدة في بداية الاتصال، في المصافحة Handshake، لإثبات هوية السيرفر، ثم يتفق الطرفان على مفاتيح جلسة Session Keys متماثلة سريعة يشفران بها كل ما بعد ذلك. والمصافحة في TLS 1.3، المعرفة في [RFC 8446](https://datatracker.ietf.org/doc/html/rfc8446?ref=arabroot.io)، تتم في دورة ذهاب وعودة واحدة Round Trip كما يلي:

1. **ClientHello**: يرسل الكلاينت الخوارزميات التي يدعمها، وجزءه من تبادل المفاتيح Key Share، واسم النطاق الذي يريده في الامتداد SNI، والسبب أن سيرفراً واحداً قد يحمل شهادات لعشرات النطاقات.
2. **ServerHello**: يرد السيرفر بالخوارزمية التي اختارها وجزئه من تبادل المفاتيح، ومن هذه اللحظة يملك الطرفان مفاتيح مشتركة، فيصبح كل ما بعدها مشفراً، بما في ذلك الشهادة نفسها، وهذا من الفروق عن TLS 1.2 الذي كان يرسل الشهادة دون تشفير.
3. **Certificate**: يرسل السيرفر شهادته والشهادات الوسيطة.
4. **CertificateVerify**: يوقع السيرفر بمفتاحه الخاص على كل رسائل المصافحة حتى هذه اللحظة، وهذا هو الإثبات أنه يملك المفتاح الخاص المقابل للشهادة، فالمهاجم يستطيع أن ينسخ شهادة GitHub لأنها عامة، ولكنه لا يستطيع أن ينتج هذا التوقيع.
5. **الفحص عند الكلاينت**: يبني السلسلة حتى شهادة رئيسية في مخزنه، ويتحقق من كل توقيع فيها، ومن أن الوقت الحالي داخل مدة كل شهادة، ومن أن اسم النطاق الذي طلبه موجود في SAN، ومن أن Extended Key Usage يسمح بـ TLS Server، ثم يتحقق من توقيع CertificateVerify بالمفتاح العام في الشهادة. فإذا فشل أي فحص أوقف الاتصال وعرض المتصفح صفحة التحذير.
6. **Finished**: يؤكد الطرفان أن أحداً لم يعدل رسائل المصافحة، ثم تبدأ البيانات المشفرة بمفاتيح الجلسة.

الصورة التالية تبين المصافحة ومكان الفحص فيها:

![مخطط تسلسلي بين متصفح وسيرفر: ClientHello مع SNI وKey Share، ثم ServerHello مع Key Share، ثم رسائل مشفرة Certificate وCertificateVerify وFinished، ثم مربع أخضر عند المتصفح فيه خطوات الفحص الخمس: السلسلة حتى مخزن الثقة، والتوقيعات، والتاريخ، والاسم في SAN، وتوقيع CertificateVerify، ثم Finished من المتصفح والبيانات المشفرة](https://arabroot.io/content/images/2026/10/certificates-04-handshake.webp)

في TLS 1.3 تصل الشهادة مشفرة، ويفحصها الكلاينت قبل أن يرسل أي بيانات للتطبيق

ولاحظ أن كل هذا يحمي الاتصال الذي يبدأ بـ HTTPS، أما الطلب الأول الذي يكتب فيه المستخدم اسم الموقع دون `https://` فيخرج عبر HTTP عادي، وهذا ما تحله الترويسة HSTS التي شرحناها في قسم [HSTS](https://arabroot.io/articles/%D8%AA%D8%B1%D9%88%D9%8A%D8%B3%D8%A7%D8%AA-%D8%A7%D9%84%D8%A3%D9%85%D8%A7%D9%86-hsts-%D9%88-csp/#hsts).

## صيغ الملفات: PEM وDER وPKCS وامتداداتها

الآن نصل إلى الجزء الذي يحير الجميع، وأول ما يجب أن تفهمه أن هناك سؤالين مختلفين عن أي ملف: **كيف كتبت البايتات؟** وهذا هو الترميز، PEM أو DER، و**ماذا يحتوي الملف؟** شهادة أو مفتاح أو طلب شهادة أو سلسلة أو حزمة كاملة، وهذا ما تحدده معايير PKCS. أما الامتداد `.crt` أو `.pem` فهو مجرد عادة يتبعها كل برنامج بطريقته، ولا يحدد أياً من الأمرين.

### DER وPEM: الملف نفسه بشكلين

الشهادة في الأصل بنية بيانات معرفة بلغة ASN.1، وتكتب بايتاتها بقواعد الترميز DER، فالملف بصيغة DER ملف ثنائي Binary. أما PEM فهو الملف نفسه بعد ترميزه بـ Base64، بين سطرين يحددان نوع المحتوى، مثل `-----BEGIN CERTIFICATE-----` و`-----END CERTIFICATE-----`، كما في [RFC 7468](https://datatracker.ietf.org/doc/html/rfc7468?ref=arabroot.io)، والسبب نفسه الذي شرحناه في مقال [الترميز Base64](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-base64-%D9%88-base32-%D9%88-base58/#mime): النص يمر في البريد ويلصق في ملفات الإعداد ولوحات التحكم، والبايتات الخام لا تمر. ولنتأكد بأنفسنا، نحول شهادة من التجربة إلى DER ونقارن:

```bash
openssl x509 -in server.crt -outform DER -out server.der
ls -l server.crt server.der
od -A x -t x1 -N 16 server.der
openssl base64 -in server.der | head -2; sed -n 2,3p server.crt
```

والمخرج سوف يكون كما يلي:

```text
-rw-r--r-- 1 root root 1038 Oct  5 23:28 server.crt
-rw-r--r-- 1 root root  726 Oct  5 23:28 server.der
000000 30 82 02 d2 30 82 02 77 a0 03 02 01 02 02 14 05
000010
MIIC0jCCAnegAwIBAgIUBcTUw7oaNj03gSyNlejIrLkVeUYwCgYIKoZIzj0EAwIw
SzELMAkGA1UEBhMCU0ExFTATBgNVBAoMDEFyYWJSb290IExhYjElMCMGA1UEAwwc
MIIC0jCCAnegAwIBAgIUBcTUw7oaNj03gSyNlejIrLkVeUYwCgYIKoZIzj0EAwIw
SzELMAkGA1UEBhMCU0ExFTATBgNVBAoMDEFyYWJSb290IExhYjElMCMGA1UEAwwc
```

في المخرج أعلاه لاحظ التالي:

- ملف DER حجمه 726 بايت، وملف PEM 1038 بايت، أي أكبر بالثلث تقريباً مع السطرين والأسطر الجديدة، وهذه زيادة Base64 المعروفة.
- ملف DER يبدأ بالبايتين `30 82`، و`30` في ASN.1 تعني SEQUENCE، فإذا فتحت ملفاً ورأيت هذين البايتين في بدايته فهو غالباً DER. ولذلك تبدأ كل شهادة PEM تقريباً بالحروف `MII`، لأنها ترميز Base64 لهذين البايتين وما بعدهما.
- أسطر Base64 من DER هي نفسها حرفاً بحرف السطور داخل ملف PEM، فالفرق في الشكل فقط والمحتوى واحد.

وإذا أردت أن ترى البنية نفسها فالأمر `openssl asn1parse -in server.crt | head -12` يعرضها، والمخرج سوف يكون كما يلي:

```text
    0:d=0  hl=4 l= 722 cons: SEQUENCE
    4:d=1  hl=4 l= 631 cons: SEQUENCE
    8:d=2  hl=2 l=   3 cons: cont [ 0 ]
   10:d=3  hl=2 l=   1 prim: INTEGER           :02
   13:d=2  hl=2 l=  20 prim: INTEGER           :05C4D4C3BA1A363D37812C8D95E8C8ACB9157946
   35:d=2  hl=2 l=  10 cons: SEQUENCE
   37:d=3  hl=2 l=   8 prim: OBJECT            :ecdsa-with-SHA256
   47:d=2  hl=2 l=  75 cons: SEQUENCE
   49:d=3  hl=2 l=  11 cons: SET
   51:d=4  hl=2 l=   9 cons: SEQUENCE
   53:d=5  hl=2 l=   3 prim: OBJECT            :countryName
   58:d=5  hl=2 l=   2 prim: PRINTABLESTRING   :SA
```

وهنا ترى الحقول التي قرأناها في شهادة GitHub بشكلها الخام: الإصدار `02` الذي يعني النسخة الثالثة، ثم الرقم التسلسلي، ثم خوارزمية التوقيع، ثم بداية اسم الجهة المصدرة.

### الامتدادات: الاسم لا يحدد الصيغة

الجدول التالي يبين الامتدادات الشائعة وما تحتويه عادة، ولاحظ كلمة «عادة»:

| الامتداد   | ما يحتويه عادة                               | الترميز المعتاد                     |
| ---------- | -------------------------------------------- | ----------------------------------- |
| .pem       | أي شيء: شهادة أو مفتاح أو سلسلة أو كلها معاً | PEM                                 |
| .crt       | شهادة، أو سلسلة شهادات                       | PEM في Linux، وقد يكون DER          |
| .cer       | شهادة                                        | DER في Windows غالباً، وقد يكون PEM |
| .der       | شهادة واحدة أو مفتاح                         | DER                                 |
| .key       | مفتاح خاص                                    | PEM غالباً                          |
| .csr       | طلب شهادة Certificate Signing Request        | PEM غالباً                          |
| .p7b و.p7c | سلسلة شهادات دون مفتاح، PKCS#7               | PEM أو DER                          |
| .pfx و.p12 | شهادة ومفتاح خاص وسلسلة بكلمة مرور، PKCS#12  | ثنائي دائماً                        |
| .jks       | مخزن مفاتيح Java القديم                      | ثنائي                               |

ولذلك إذا وصلك ملف لا تعرف ما فيه فلا تحكم عليه من اسمه، وإنما افتحه: إذا كان نصاً يبدأ بـ `-----BEGIN` فهو PEM والسطر نفسه يخبرك بنوعه، وإذا كان ثنائياً يبدأ بـ `30 82` فهو DER أو PKCS#12\. ولاحظ أن OpenSSL 3 يتعرف على الصيغة تلقائياً في أغلب الأوامر، فإذا نسخنا ملف DER باسم `.crt` فسوف يقرؤه دون مشكلة:

```bash
cp server.der mystery.crt; openssl x509 -in mystery.crt -noout -subject
```

```text
subject=CN=app.lab.test
```

ولكن Nginx وHAProxy وأغلب البرامج لا تفعل ذلك، فهي تنتظر PEM ولا شيء غيره، وسوف نرى الخطأ الذي يظهر حينها في قسم [الأخطاء الشائعة](#common-errors). وهذا سبب الحيرة المعتادة: الملف يفتح في OpenSSL ويرفضه السيرفر.

### المفتاح الخاص: PKCS#1 وPKCS#8 والمفتاح المشفر

المفتاح الخاص يكتب أيضاً بأكثر من صيغة، وتعرفها من السطر الأول:

```bash
openssl pkey -in server.key -traditional -out server-pkcs1.key
openssl pkey -in server.key -aes256 -passout pass:LabPass123 -out server-enc.key
head -1 server.key server-pkcs1.key server-enc.key root.key
```

والمخرج سوف يكون كما يلي:

```text
==> server.key <==
-----BEGIN PRIVATE KEY-----

==> server-pkcs1.key <==
-----BEGIN RSA PRIVATE KEY-----

==> server-enc.key <==
-----BEGIN ENCRYPTED PRIVATE KEY-----

==> root.key <==
-----BEGIN PRIVATE KEY-----
```

في المخرج أعلاه لاحظ التالي:

- `BEGIN PRIVATE KEY` هي صيغة PKCS#8 المعرفة في [RFC 5958](https://datatracker.ietf.org/doc/html/rfc5958?ref=arabroot.io)، وهي صيغة عامة لأي نوع من المفاتيح، وفيها حقل يذكر الخوارزمية، ولذلك جاء مفتاح RSA `server.key` ومفتاح EC `root.key` بالسطر نفسه، وهي ما يكتبه OpenSSL 3 افتراضياً.
- `BEGIN RSA PRIVATE KEY` هي الصيغة القديمة PKCS#1 من [RFC 8017](https://datatracker.ietf.org/doc/html/rfc8017?ref=arabroot.io)، وهي لمفاتيح RSA فقط، وتراها في المفاتيح التي ولدت بإصدارات OpenSSL القديمة، ومثلها لمفاتيح EC الصيغة `BEGIN EC PRIVATE KEY`. والمفتاح نفسه لم يتغير، والأمر `openssl pkey` يحول بينهما في الاتجاهين.
- `BEGIN ENCRYPTED PRIVATE KEY` هو PKCS#8 مشفر بكلمة مرور، وأي برنامج يقرؤه يجب أن يسأل عن كلمة المرور، ولذلك لا تستخدمه مع Nginx أو Nginx Proxy Manager الذي يرفض المفتاح المحمي بعبارة مرور، والسبب أن السيرفر سوف يتوقف عند كل إعادة تشغيل منتظراً شخصاً يكتبها. احم المفتاح بصلاحيات الملف `chmod 600` بدلاً من ذلك.

### PKCS#7: سلسلة شهادات دون مفتاح

الملف `.p7b` بصيغة PKCS#7 من [RFC 2315](https://datatracker.ietf.org/doc/html/rfc2315?ref=arabroot.io) يجمع عدة شهادات في ملف واحد ولا يحمل مفتاحاً خاصاً أبداً، وتراه عندما تصدر الشهادة من بعض الجهات التجارية أو عندما تصدرها من Windows مع السلسلة. ونصنعه ونقرؤه كما يلي:

```bash
openssl crl2pkcs7 -nocrl -certfile fullchain.pem -out chain.p7b
head -1 chain.p7b
openssl pkcs7 -in chain.p7b -print_certs -noout
```

والمخرج سوف يكون كما يلي:

```text
-----BEGIN PKCS7-----
subject=CN=app.lab.test
issuer=C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Intermediate CA

subject=C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Intermediate CA
issuer=C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Root CA
```

ولاحظ أن الأمر الأخير مفيد مع أي ملف سلسلة PEM أيضاً، فهو يعرض ترتيب الشهادات داخله، وقد استخدمناه في دليل HAProxy لفحص [ترتيب الملف المجمع](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-haproxy-load-balancer/#pem-order). فإذا أعطتك الجهة ملف `.p7b` وأردت أن تستخدمه مع Nginx فحوله إلى PEM بالأمر `openssl pkcs7 -in chain.p7b -print_certs -out chain.pem`.

### PKCS#12: الشهادة والمفتاح والسلسلة في ملف واحد

الملف `.pfx` أو `.p12` بصيغة PKCS#12 من [RFC 7292](https://datatracker.ietf.org/doc/html/rfc7292?ref=arabroot.io) هو الحزمة الكاملة: المفتاح الخاص وشهادته والشهادات الوسيطة، كلها مشفرة بكلمة مرور في ملف ثنائي واحد، وهي الصيغة التي يطلبها Windows وIIS عند الاستيراد، وتستخدمها Java وكثير من الأجهزة. ونصنعها من ملفات PEM ثم نقرأ محتواها:

```bash
openssl pkcs12 -export -inkey server.key -in server.crt -certfile inter.crt -name app.lab.test -passout pass:LabPass123 -out server.p12
openssl pkcs12 -in server.p12 -passin pass:LabPass123 -info -noout
```

والمخرج سوف يكون كما يلي:

```text
MAC: sha256, Iteration 2048
MAC length: 32, salt length: 8
PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
Certificate bag
Certificate bag
PKCS7 Data
Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
```

في المخرج أعلاه لاحظ التالي:

- في الملف شهادتان `Certificate bag`، شهادة النطاق والوسيطة، ومفتاح واحد `Shrouded Keybag` أي مفتاح مشفر.
- التشفير AES-256-CBC مع PBKDF2، وهو [الافتراضي في OpenSSL 3](https://docs.openssl.org/3.5/man1/openssl-pkcs12/?ref=arabroot.io)، بينما الإصدارات القديمة كانت تستخدم RC2 و3DES. وبعض الأنظمة القديمة لا تفتح الملف الحديث، فإذا احتجت ملفاً لنظام قديم فأضف الخيار `-legacy` عند التصدير.

والاستخراج في الاتجاه العكسي، من PFX وصلك من Windows إلى ملفات PEM لسيرفر Linux، يتم بثلاثة أوامر:

```bash
openssl pkcs12 -in server.p12 -passin pass:LabPass123 -clcerts -nokeys -out cert-from-p12.pem
openssl pkcs12 -in server.p12 -passin pass:LabPass123 -cacerts -nokeys -out chain-from-p12.pem
openssl pkcs12 -in server.p12 -passin pass:LabPass123 -nocerts -noenc -out key-from-p12.pem
grep -h BEGIN cert-from-p12.pem chain-from-p12.pem key-from-p12.pem
head -4 cert-from-p12.pem
```

والمخرج سوف يكون كما يلي:

```text
-----BEGIN CERTIFICATE-----
-----BEGIN CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
Bag Attributes
    localKeyID: 43 CD A4 69 90 5B 6D 4B E3 23 91 71 C5 02 80 C8 46 5E F8 71
    friendlyName: app.lab.test
subject=CN=app.lab.test
```

في الأوامر أعلاه لاحظ التالي:

- `-clcerts` يخرج شهادة صاحب المفتاح فقط، و`-cacerts` الشهادات الوسيطة فقط، و`-nocerts -noenc` المفتاح دون تشفير، والخيار `-noenc` هو الاسم الجديد لـ `-nodes` الذي [أصبح قديماً منذ OpenSSL 3.0](https://docs.openssl.org/3.5/man1/openssl-pkcs12/?ref=arabroot.io).
- يضيف OpenSSL أسطراً قبل كل كتلة مثل `Bag Attributes` و`subject=`، وأغلب البرامج تتجاهل ما قبل `-----BEGIN`، فإذا رفضها برنامج فنظف الملف بالأمر `openssl x509 -in cert-from-p12.pem -out cert.pem`.
- المفتاح المستخرج غير مشفر، فاحذفه أو احمه بالصلاحيات فوراً، ولا تتركه في مجلد التنزيلات.

### مخازن Java: JKS وPKCS12

تطبيقات Java لا تقرأ ملفات PEM مباشرة في أغلب الحالات، وإنما تقرأ مخزن مفاتيح KeyStore، وكانت صيغته الافتراضية JKS، وهي صيغة خاصة بـ Java، ثم [أصبحت PKCS12 هي الافتراضية منذ JDK 9](https://openjdk.org/jeps/229?ref=arabroot.io)، أي أن ملف `.p12` الذي صنعناه بـ OpenSSL هو مخزن Java صالح كما هو. ونتأكد بالأداة `keytool` في Container من Eclipse Temurin 25:

```bash
keytool -list -keystore server.p12 -storepass LabPass123
keytool -list -v -keystore server.p12 -storepass LabPass123 | grep -E "Alias|Entry type|chain length|^Owner|^Issuer|Valid"
```

والمخرج سوف يكون كما يلي:

```text
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

app.lab.test, Oct 5, 2026, PrivateKeyEntry,
Certificate fingerprint (SHA-256): A1:92:B9:6A:5B:BE:7A:A1:AC:EC:54:34:BE:32:28:73:34:1C:C4:24:51:19:B4:6D:82:3A:08:81:27:DB:77:2F
Alias name: app.lab.test
Entry type: PrivateKeyEntry
Certificate chain length: 2
Owner: CN=app.lab.test
Issuer: CN=ArabRoot Lab Intermediate CA, O=ArabRoot Lab, C=SA
Valid from: Mon Oct 05 23:28:27 GMT 2026 until: Sat Nov 21 23:28:27 GMT 2026
Owner: CN=ArabRoot Lab Intermediate CA, O=ArabRoot Lab, C=SA
Issuer: CN=ArabRoot Lab Root CA, O=ArabRoot Lab, C=SA
Valid from: Mon Oct 05 23:28:09 GMT 2026 until: Sat Oct 04 23:28:09 GMT 2031
```

ولاحظ أن الاسم `-name app.lab.test` الذي وضعناه عند التصدير أصبح الاسم المستعار Alias الذي يطلبه إعداد التطبيق عادة، وأن السلسلة طولها 2\. وفي Java نوعان من المخازن يجب ألا تخلط بينهما: مخزن المفاتيح KeyStore وفيه مفتاح التطبيق وشهادته `PrivateKeyEntry`، ومخزن الثقة TrustStore وفيه الشهادات الرئيسية التي يثق بها التطبيق عندما يتصل بغيره `trustedCertEntry`، ونضيف إليه شهادة جهتنا الداخلية كما يلي:

```bash
keytool -importcert -noprompt -alias arabroot-lab-root -file root.crt -keystore truststore.p12 -storepass LabPass123
keytool -list -keystore truststore.p12 -storepass LabPass123
```

```text
Certificate was added to keystore
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

arabroot-lab-root, Oct 5, 2026, trustedCertEntry,
Certificate fingerprint (SHA-256): A0:48:1E:8E:B1:C1:78:20:D4:22:AC:77:EC:E7:63:2F:8F:EF:C8:03:A2:A6:3A:B9:8B:3A:D7:45:4C:46:99:75
```

وإذا طلب تطبيق قديم ملف JKS فالتحويل بالأمر `keytool -importkeystore`، وسوف تلاحظ أن keytool نفسه ينصحك بالعكس:

```bash
keytool -importkeystore -srckeystore server.p12 -srcstorepass LabPass123 -destkeystore server.jks -deststoretype JKS -deststorepass LabPass123
```

```text
Importing keystore server.p12 to server.jks...
Entry for alias app.lab.test successfully imported.
Import command completed:  1 entries successfully imported, 0 entries failed or cancelled

Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore server.jks -destkeystore server.jks -deststoretype pkcs12".
```

الصورة التالية تجمع الصيغ كلها وما يحمله كل منها:

![خريطة للصيغ: في الأعلى ملف DER ثنائي يبدأ بالبايتين 30 82 يتحول عبر Base64 إلى ملف PEM نصي بين سطري BEGIN وEND، وفي الأسفل جدول مرسوم فيه PKCS#1 وPKCS#8 وPKCS#7 وPKCS#12 وJKS، ولكل منها علامات لما يحمله: مفتاح خاص، شهادة، سلسلة، كلمة مرور، مع الامتدادات الشائعة لكل صيغة](https://arabroot.io/content/images/2026/10/certificates-05-formats.webp)

PEM وDER طريقتان لكتابة البايتات، وPKCS تحدد ما بداخل الملف، والامتداد مجرد عادة

### ملفات Let's Encrypt الأربعة: أيها تستخدم؟

إذا استخدمت Certbot مباشرة، كما فعلنا في دليل [HAProxy](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-haproxy-load-balancer/#tls-certbot)، فسوف تجد في المجلد `/etc/letsencrypt/live/example.com/` أربعة ملفات، كما في [توثيق Certbot](https://eff-certbot.readthedocs.io/en/stable/using.html?ref=arabroot.io#where-are-my-certificates):

- `**privkey.pem**`: المفتاح الخاص، وهو الملف السري الوحيد بينها، ويوضع في `ssl_certificate_key` في Nginx و`SSLCertificateKeyFile` في Apache.
- `**fullchain.pem**`: شهادة النطاق ثم الشهادة الوسيطة، وهو الملف الذي تضعه في `ssl_certificate` في Nginx و`SSLCertificateFile` في Apache 2.4.8 وما بعده، وفي أغلب البرامج الأخرى.
- **`cert.pem`**: شهادة النطاق وحدها، ولا تستخدمه كشهادة للسيرفر، والسبب أنه يسبب خطأ السلسلة الناقصة الذي شرحناه.
- `**chain.pem**`: الشهادة الوسيطة وحدها، ويحتاجه Apache القديم مع `SSLCertificateChainFile`، أو برنامج يطلب الشهادة والسلسلة في حقلين منفصلين.

ولاحظ أن هذه الملفات روابط رمزية Symlinks إلى أحدث نسخة في مجلد `archive`، لذلك يطلب التوثيق أن تشير إعدادات السيرفر إليها مباشرة ولا تنسخها، فالنسخة المنسوخة لن تتجدد. الصورة التالية تبين كل ملف ومكانه:

![مجلد live/example.com فيه أربعة ملفات: privkey.pem بمفتاح ذهبي، وcert.pem بشهادة واحدة، وchain.pem بالشهادة الوسيطة، وfullchain.pem بالشهادتين معاً، وأسهم من fullchain.pem وprivkey.pem إلى Nginx وApache، ومن دمج الملفين إلى HAProxy، ومن التحويل إلى PKCS#12 إلى IIS وJava، وعلامة خطأ على استخدام cert.pem وحده](https://arabroot.io/content/images/2026/10/certificates-06-letsencrypt-files.webp)

أغلب البرامج تحتاج fullchain.pem وprivkey.pem، وHAProxy يريدهما في ملف واحد، وWindows وJava يريدان PKCS#12

## تجربة عملية: نبني جهة إصدار صغيرة ونصدر شهادة

الآن سوف نطبق كل ما سبق بأيدينا، فنبني جهة إصدار داخلية بشهادة رئيسية وشهادة وسيطة، ثم نصدر شهادة لسيرفر، ونتحقق منها، ونشغل سيرفر TLS بها. نفذنا التجربة في Container من Debian 13.7 فيه OpenSSL 3.5.7 وcurl 8.14.1، وكل الأوامر تعمل على أي توزيعة Linux فيها OpenSSL 3، والأسماء `app.lab.test` وكلمة المرور `LabPass123` للتجربة فقط.

### الخطوة 1: الشهادة الرئيسية

```bash
mkdir pki && cd pki
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out root.key
openssl req -x509 -new -key root.key -sha256 -days 3650 -subj "/C=SA/O=ArabRoot Lab/CN=ArabRoot Lab Root CA" -addext "basicConstraints=critical,CA:TRUE" -addext "keyUsage=critical,keyCertSign,cRLSign" -out root.crt
openssl x509 -in root.crt -noout -subject -issuer -dates -ext basicConstraints,keyUsage
```

والمخرج سوف يكون كما يلي:

```text
subject=C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Root CA
issuer=C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Root CA
notBefore=Oct  5 23:27:46 2026 GMT
notAfter=Oct  2 23:27:46 2036 GMT
X509v3 Basic Constraints: critical
    CA:TRUE
X509v3 Key Usage: critical
    Certificate Sign, CRL Sign
```

في الإعداد أعلاه لاحظ التالي:

- `req -x509` يصنع شهادة موقعة من نفسها مباشرة، ولذلك Subject وIssuer متساويان، وهذه علامة كل شهادة رئيسية.
- `CA:TRUE` مع `keyCertSign` يسمحان لهذه الشهادة بتوقيع شهادات أخرى، ومدتها عشر سنوات لأنها لا تستخدم إلا نادراً.
- في جهة حقيقية يولد `root.key` على جهاز غير متصل بالشبكة ويحفظ في HSM أو على الأقل مشفراً في مكان آمن، ولا يبقى على سيرفر يعمل.

### الخطوة 2: الشهادة الوسيطة

نكتب امتدادات الشهادة الوسيطة في ملف `inter.ext`:

```ini
basicConstraints = critical, CA:TRUE, pathlen:0
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid
```

ثم نولد مفتاحها وطلبها ونوقعه بالشهادة الرئيسية:

```bash
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out inter.key
openssl req -new -key inter.key -subj "/C=SA/O=ArabRoot Lab/CN=ArabRoot Lab Intermediate CA" -out inter.csr
openssl x509 -req -in inter.csr -CA root.crt -CAkey root.key -days 1825 -sha256 -extfile inter.ext -out inter.crt
openssl x509 -in inter.crt -noout -subject -issuer -serial -ext basicConstraints
```

والمخرج سوف يكون كما يلي:

```text
Certificate request self-signature ok
subject=C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Intermediate CA
subject=C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Intermediate CA
issuer=C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Root CA
serial=5356DBAB29B46324544B208B3D27E615CB39AF7C
X509v3 Basic Constraints: critical
    CA:TRUE, pathlen:0
```

ولاحظ `pathlen:0`، ومعناه أن هذه الشهادة الوسيطة تستطيع أن توقع شهادات مواقع فقط، ولا تستطيع أن توقع شهادة وسيطة أخرى تحتها، وأن OpenSSL 3 ولد رقماً تسلسلياً عشوائياً بطول 20 بايت دون أن نطلب.

### الخطوة 3: مفتاح السيرفر وطلب الشهادة CSR

طلب توقيع الشهادة Certificate Signing Request واختصاراً CSR هو ما ترسله إلى أي جهة إصدار، تجارية كانت أو داخلية: فيه المفتاح العام والأسماء المطلوبة، وموقع بالمفتاح الخاص ليثبت أنك تملكه، والمفتاح الخاص نفسه لا يغادر سيرفرك أبداً. سوف نستخدم RSA هنا حتى نرى الفرق في الصيغ لاحقاً:

```bash
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out server.key
openssl req -new -key server.key -subj "/CN=app.lab.test" -addext "subjectAltName=DNS:app.lab.test,DNS:www.app.lab.test" -out server.csr
openssl req -in server.csr -noout -text -verify
```

والمخرج سوف يكون كما يلي، بعد اختصار أرقام المفتاح والتوقيع:

```text
Certificate request self-signature verify OK
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: CN=app.lab.test
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:d4:70:ef:ee:9a:de:f9:94:80:b5:cb:40:20:92:
                    …
                Exponent: 65537 (0x10001)
        Attributes:
            Requested Extensions:
                X509v3 Subject Alternative Name:
                    DNS:app.lab.test, DNS:www.app.lab.test
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        1b:14:ae:3a:96:5b:bc:b2:13:f2:01:ea:59:e4:eb:0f:82:69:
        …
```

ولاحظ `self-signature verify OK`، أي أن الطلب موقع بالمفتاح الخاص المقابل للمفتاح العام فيه، وأن الأسماء في `Requested Extensions` هي ما سوف يظهر في SAN إذا وافقت الجهة.

### الخطوة 4: توقيع شهادة السيرفر

نكتب امتدادات شهادة السيرفر في `server.ext`:

```ini
basicConstraints = critical, CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid
```

ثم توقع الشهادة الوسيطة الطلب، ونجعل المدة 47 يوماً لأنها المدة القصوى التي سوف تصل إليها الشهادات العامة في 2029:

```bash
openssl x509 -req -in server.csr -CA inter.crt -CAkey inter.key -days 47 -sha256 -copy_extensions copy -extfile server.ext -out server.crt
openssl x509 -in server.crt -noout -subject -issuer -dates -ext subjectAltName,keyUsage,extendedKeyUsage,basicConstraints
```

والمخرج سوف يكون كما يلي:

```text
Certificate request self-signature ok
subject=CN=app.lab.test
subject=CN=app.lab.test
issuer=C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Intermediate CA
notBefore=Oct  5 23:28:27 2026 GMT
notAfter=Nov 21 23:28:27 2026 GMT
X509v3 Subject Alternative Name:
    DNS:app.lab.test, DNS:www.app.lab.test
X509v3 Basic Constraints: critical
    CA:FALSE
X509v3 Key Usage: critical
    Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
    TLS Web Server Authentication
```

الخيار `-copy_extensions copy` ينقل SAN من الطلب إلى الشهادة، وهذا مناسب في التجربة لأننا نحن من كتب الطلب، أما الجهة الحقيقية فلا تنسخ ما في الطلب دون فحص، وإنما تتحقق من كل اسم فيه قبل أن تضعه في الشهادة، وإلا لطلب أي شخص شهادة لـ `google.com`.

### الخطوة 5: التحقق من السلسلة

```bash
openssl verify -CAfile root.crt server.crt
openssl verify -CAfile root.crt -untrusted inter.crt server.crt
cat server.crt inter.crt > fullchain.pem
grep -c "BEGIN CERTIFICATE" fullchain.pem
```

والمخرج سوف يكون كما يلي:

```text
CN=app.lab.test
error 20 at 0 depth lookup: unable to get local issuer certificate
error server.crt: verification failed
server.crt: OK
2
```

في المخرج أعلاه لاحظ التالي:

- في المحاولة الأولى أعطينا OpenSSL الشهادة الرئيسية فقط، فلم يجد من وقع `server.crt`، لأن الذي وقعها هو الشهادة الوسيطة، فظهر الخطأ 20، وهذا بالضبط ما يحدث لأي كلاينت عندما لا يرسل السيرفر الشهادة الوسيطة.
- الخيار `-untrusted inter.crt` يعطي الشهادة الوسيطة كحلقة في السلسلة دون أن يثق بها كجهة رئيسية، فاكتملت السلسلة ونجح التحقق.
- الملف `fullchain.pem` هو شهادة السيرفر ثم الوسيطة بهذا الترتيب، وهو ما سوف يقرؤه السيرفر.

### الخطوة 6: سيرفر TLS دون الشهادة الوسيطة ثم معها

أضفنا الاسم `app.lab.test` إلى `/etc/hosts` ليشير إلى `127.0.0.1`، ثم شغلنا سيرفر TLS تجريبياً بالأمر `openssl s_server` على المنفذ 8443، مع شهادة السيرفر وحدها كما يفعل من يضع `cert.pem` بدلاً من `fullchain.pem`:

```bash
openssl s_server -accept 127.0.0.1:8443 -cert server.crt -key server.key -www &
echo | openssl s_client -connect 127.0.0.1:8443 -servername app.lab.test -CAfile root.crt -verify_hostname app.lab.test 2>&1 | grep -E "depth|verify|Verif|Certificate chain| [0-9] s:|   i:"
curl -sS --cacert root.crt https://app.lab.test:8443/ -o /dev/null
```

والمخرج سوف يكون كما يلي:

```text
depth=0 CN=app.lab.test
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN=app.lab.test
verify error:num=21:unable to verify the first certificate
verify return:1
depth=0 CN=app.lab.test
verify return:1
Certificate chain
 0 s:CN=app.lab.test
   i:C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Intermediate CA
Verification error: unable to verify the first certificate
Verify return code: 21 (unable to verify the first certificate)
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the webpage mentioned above.
```

ولاحظ أن الكلاينت يثق بالشهادة الرئيسية `root.crt`، ومع ذلك فشل، لأن السلسلة فيها شهادة واحدة فقط `0 s:`، ولا يعرف الكلاينت الشهادة الوسيطة التي تربطها بالشهادة الرئيسية. الآن نوقف السيرفر ونشغله مرة أخرى مع الشهادة الوسيطة بالخيار `-cert_chain`:

```bash
pkill -x openssl
openssl s_server -accept 127.0.0.1:8443 -cert server.crt -key server.key -cert_chain inter.crt -www &
echo | openssl s_client -connect 127.0.0.1:8443 -servername app.lab.test -CAfile root.crt -verify_hostname app.lab.test 2>&1 | grep -E "depth|verify|Verif|Certificate chain| [0-9] s:|   i:|Protocol|Cipher is"
curl -sS --cacert root.crt https://app.lab.test:8443/ -o /dev/null -w "%{http_code} %{ssl_verify_result}\n"
```

والمخرج سوف يكون كما يلي:

```text
depth=2 C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Root CA
verify return:1
depth=1 C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Intermediate CA
verify return:1
depth=0 CN=app.lab.test
verify return:1
Certificate chain
 0 s:CN=app.lab.test
   i:C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Intermediate CA
 1 s:C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Intermediate CA
   i:C=SA, O=ArabRoot Lab, CN=ArabRoot Lab Root CA
Verification: OK
Verified peername: app.lab.test
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
Verify return code: 0 (ok)
200 0
```

والآن السلسلة شهادتان، والتحقق وصل إلى العمق 2، و`Verified peername` يؤكد أن الاسم في SAN، وcurl أعاد 200\. والفرق بين الحالتين سطر واحد في الإعداد، وهذا السطر هو الفرق بين `cert.pem` و`fullchain.pem`.

### الخطوة 7: إضافة جهتك الداخلية إلى مخزن الثقة

في المحاولات السابقة مررنا `--cacert root.crt` لكل أمر، أما إذا أردت أن يثق كل برنامج على السيرفر بجهتك الداخلية فأضف الشهادة الرئيسية إلى مخزن النظام، وفي Debian وUbuntu يتم ذلك كما يلي:

```bash
cp root.crt /usr/local/share/ca-certificates/arabroot-lab-root.crt && update-ca-certificates
curl -sS https://app.lab.test:8443/ -o /dev/null -w "%{http_code}\n"
```

```text
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt, it does not contain exactly one certificate or CRL
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
200
```

ولاحظ أن الملف يجب أن ينتهي بالامتداد `.crt` وأن يكون PEM، وإلا تجاهله `update-ca-certificates`، والتحذير عن `ca-certificates.crt` طبيعي لأنه ملف الحزمة المجمع. وتذكر أن Container يحمل مخزنه الخاص، فإضافة الشهادة إلى السيرفر لا تضيفها إلى ال Containers التي عليه، ويجب أن تضيفها في ال Docker Image أو تربطها بها، ولكل لغة أحياناً مخزنها الخاص كما رأينا مع Java.

### الخطوة 8: هل هذا المفتاح لهذه الشهادة؟

عندما تجد على السيرفر عدة ملفات `.key` و`.crt` ولا تعرف أي مفتاح لأي شهادة، فقارن المفتاح العام في الشهادة بالمفتاح العام المستخرج من المفتاح الخاص، فإذا تساوى الهاش فهما زوج واحد:

```bash
openssl x509 -in server.crt -noout -pubkey | openssl sha256
openssl pkey -in server.key -pubout | openssl sha256
openssl pkey -in inter.key -pubout | openssl sha256
```

```text
SHA2-256(stdin)= 565f2e9b344b5164ab29353ee23388e6dd6d8fbafc8cb77021b1ab35883e6d41
SHA2-256(stdin)= 565f2e9b344b5164ab29353ee23388e6dd6d8fbafc8cb77021b1ab35883e6d41
SHA2-256(stdin)= 7b25805af432e2622a014a640292aa74604f6701f46922db0287524d48753b7f
```

السطران الأولان متساويان، فالمفتاح `server.key` لهذه الشهادة، والثالث مختلف. وقد تجد في الشروحات القديمة مقارنة Modulus بالأمرين `openssl x509 -noout -modulus` و`openssl rsa -noout -modulus`، وهذه تعمل مع RSA فقط وأعطت في تجربتنا الهاش نفسه للطرفين، أما مقارنة المفتاح العام فتعمل مع كل الأنواع، ولذلك هي الأنسب.

### الخطوة 9: متى تنتهي الشهادة؟

الخيار `-checkend` يأخذ عدد ثوان ويخبرك هل تنتهي الشهادة خلالها، ويعيد رمز خروج Exit Code غير صفر إذا كانت ستنتهي، فيصلح لسكربت مراقبة يرسل تنبيهاً قبل 30 يوماً، أي 2592000 ثانية:

```bash
openssl x509 -in server.crt -noout -enddate -checkend 2592000
openssl x509 -in expired.crt -noout -enddate -checkend 0
echo | openssl s_client -connect github.com:443 -servername github.com 2>/dev/null | openssl x509 -noout -enddate
```

```text
notAfter=Nov 21 23:28:27 2026 GMT
Certificate will not expire
notAfter=Sep 15 00:00:00 2026 GMT
Certificate will expire
notAfter=Nov 29 23:59:59 2026 GMT
```

الأمر الأخير يقرأ تاريخ انتهاء شهادة أي موقع مباشرة من السيرفر، وهو أصدق من الملف على القرص، لأنه يكشف أيضاً السيرفر الذي جددت شهادته ولم يعد تحميلها. أما `expired.crt` فشهادة صنعناها منتهية عمداً بالخيارين `-not_before` و`-not_after` الموجودين في [OpenSSL 3.4](https://docs.openssl.org/3.4/man1/openssl-x509/?ref=arabroot.io) وما بعده، حتى نرى الخطأ في القسم التالي.

### إزالة التجربة

إذا نفذت التجربة على جهازك فاحذف المجلد `pki`، واحذف `/usr/local/share/ca-certificates/arabroot-lab-root.crt` ثم نفذ `update-ca-certificates --fresh`، والسبب أن أي شخص يحصل على `root.key` أو `inter.key` من هذا المجلد يستطيع أن يصدر شهادة لأي موقع وسوف يثق بها جهازك.

## أين يوضع كل ملف؟

بعد أن عرفت الصيغ أصبح السؤال العملي: ماذا يطلب كل برنامج؟ والجواب في أغلب الحالات PEM، والفرق في عدد الملفات وترتيبها:

- **Nginx وNginx Proxy Manager**: ملفان PEM، السلسلة الكاملة في `ssl_certificate` والمفتاح في `ssl_certificate_key`، وفي واجهة NPM ثلاثة حقول للشهادة المخصصة: المفتاح والشهادة والشهادة الوسيطة، كما في قسم [رفع شهادة مخصصة](https://arabroot.io/articles/nginx-proxy-manager-%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-tls/#wildcard-certificate).
- **Traefik وCaddy**: يصدران الشهادات ويجددانها بأنفسهما عبر ACME ويحفظانها في ملف `acme.json` أو في مجلد البيانات، ولا تحتاج إلى أي ملف إلا إذا أردت شهادة من جهة أخرى، فتعطيهما حينها ملفي PEM، وتفاصيل التخزين في دليل [Traefik](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-traefik-reverse-proxy/#folders) ودليل [Caddy](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-caddy-reverse-proxy/#storage).
- **HAProxy**: ملف PEM واحد فيه شهادة النطاق ثم الوسيطة ثم المفتاح، أي `cat fullchain.pem privkey.pem`، وقد شرحنا السكربت الذي يجهزه بعد كل تجديد في دليل [HAProxy](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-haproxy-load-balancer/#deploy-hook).
- **Apache**: منذ 2.4.8 تكفي السلسلة الكاملة في `SSLCertificateFile` والمفتاح في `SSLCertificateKeyFile`، وأصبح `SSLCertificateChainFile` [قديماً Deprecated](https://httpd.apache.org/docs/2.4/mod/mod%5Fssl.html?ref=arabroot.io#sslcertificatefile).
- **IIS وWindows**: ملف PFX واحد يستورد إلى مخزن الشهادات في النظام، فإذا كانت لديك ملفات PEM فاجمعها بأمر `openssl pkcs12 -export` كما في [قسم PKCS#12](#pkcs12)، وإذا رفض نظام Windows قديم الملف فأعد التصدير مع `-legacy`.
- **تطبيقات Java**: مخزن PKCS12، وأحياناً JKS في التطبيقات القديمة، مع كلمة المرور والاسم المستعار Alias في إعداد التطبيق، ومخزن ثقة منفصل إذا كان التطبيق يتصل بخدمات داخلية بشهادات من جهتك.
- **Kubernetes**: [Secret من النوع kubernetes.io/tls](https://kubernetes.io/docs/concepts/configuration/secret/?ref=arabroot.io) فيه المفتاحان `tls.crt` و`tls.key`، والقيم ملفات PEM نفسها بعد ترميز Base64 مرة ثانية، وهذا ترميز وليس تشفيراً كما شرحنا في مقال [الترميز](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-base64-%D9%88-base32-%D9%88-base58/#not-encryption). والأمر `kubectl create secret tls app-tls --cert=fullchain.pem --key=privkey.pem` يقوم بالترميز عنك.
- **سيرفرات البريد**: Postfix وDovecot يقرآن PEM أيضاً، السلسلة الكاملة والمفتاح، كما في قسم [شهادة TLS](https://arabroot.io/articles/%D8%AE%D8%A7%D8%AF%D9%85-%D8%A8%D8%B1%D9%8A%D8%AF-postfix-%D9%88-dovecot-%D9%8A%D8%AF%D9%88%D9%8A%D8%A7/#tls) في دليل Postfix وDovecot.

وقد يتساءل البعض: متى أحتاج إلى جهة إصدار داخلية مثل التي بنيناها؟ والإجابة أن الشهادات العامة لا تصدر إلا لنطاقات عامة، فإذا كانت لديك أسماء داخلية مثل `db.lan`، أو اتصالات بين خدماتك تريدها مشفرة بشهادات للطرفين mTLS، أو أجهزة لا تصل إلى الإنترنت، فأنت تحتاج إلى جهة داخلية. وبناؤها يدوياً بـ OpenSSL كما فعلنا مناسب للفهم وللمختبر، أما في الإنتاج Production فأداة مثل [step-ca](https://smallstep.com/docs/step-ca/?ref=arabroot.io) تعطيك جهة داخلية تدعم ACME، فيجدد Caddy وTraefik وCertbot الشهادات منها تلقائياً كما يفعلون مع Let's Encrypt. وأما للنطاقات العامة فلا يوجد سبب اليوم لشراء شهادة DV أو تجديدها يدوياً، فـ Let's Encrypt مع ACME هي [الحل الأنسب](https://letsencrypt.org/docs/challenge-types/?ref=arabroot.io)، بالتحقق عبر HTTP أو عبر DNS للشهادات الشاملة.

💡

شهادات SSH التي شرحناها في [دليل IAM وPAM](https://arabroot.io/articles/%D8%A7%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D9%87%D9%88%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D9%88%D8%B5%D9%88%D9%84-iam-%D9%88%D8%A7%D9%84%D9%88%D8%B5%D9%88%D9%84-%D8%A7%D9%84%D9%85%D9%85%D9%8A%D8%B2-pam/#demo-ca) تقوم على الفكرة نفسها، جهة توقع مفتاح المستخدم لمدة محددة، ولكنها صيغة خاصة بـ OpenSSH وليست X.509، فلا تفتحها أوامر `openssl` ولا تصلح لـ TLS، ولا تصلح شهادة TLS لـ SSH.

## الأخطاء الشائعة وكيف تقرؤها

كل الأخطاء التالية ظهرت لنا في التجربة نفسها، وأغلبها يظهر في curl برمز الخروج 60 مع سطر يحدد السبب، فاقرأ السطر الأول ولا تتوقف عند رمز الخطأ.

1. **الشهادة منتهية**: `curl: (60) SSL certificate problem: certificate has expired`، أو في `openssl verify` الخطأ `error 10 at 0 depth lookup: certificate has expired`. والعمق يخبرك أي شهادة انتهت، فالعمق 0 شهادتك أنت، والسبب في الغالب أن التجديد الآلي فشل أو أن السيرفر لم يعد تحميل الشهادة الجديدة، فافحص التاريخ من السيرفر نفسه بأمر `s_client` في [الخطوة 9](#lab-expiry).
2. **السلسلة ناقصة**: `unable to get local issuer certificate` أو `unable to verify the first certificate`. السيرفر يرسل `cert.pem` بدلاً من `fullchain.pem` في أغلب الحالات، والدليل أن `s_client` يعرض شهادة واحدة في `Certificate chain`. وإذا كانت السلسلة كاملة وظهر الخطأ نفسه فالشهادة من جهة داخلية لم تضف إلى مخزن الثقة عند الكلاينت.
3. **الاسم لا يطابق**: `curl: (60) SSL: no alternative certificate subject name matches target hostname 'wrong.lab.test'`، أو `Verify return code: 62 (hostname mismatch)` في OpenSSL. الاسم الذي كتبته غير موجود في SAN، فإما أن الشهادة لم تشمله، أو أن السيرفر أرسل شهادة افتراضية لأنه لم يتعرف على SNI، كما في رسالة TRAEFIK DEFAULT CERT في [دليل Traefik](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-traefik-reverse-proxy/#certificate-not-issued).
4. **شهادة موقعة ذاتياً**: `curl: (60) SSL certificate problem: self-signed certificate` عندما يرسل السيرفر شهادة صنعها لنفسه كما في `openssl req -x509`. والحل شهادة من جهة عامة أو داخلية، ولا تحل المشكلة بالخيار `curl -k` أو `verify=False` في الكود، والسبب أنها تعطل التحقق من الهوية بالكامل فتعود إلى المشكلة التي بدأنا بها المقال.
5. **شهادة موقعة ذاتياً داخل السلسلة**: `verify error:num=19:self-signed certificate in certificate chain`. السلسلة تنتهي بشهادة رئيسية لا يعرفها الكلاينت، وتراه كثيراً في الشركات التي تفحص حركة HTTPS بجهاز وسيط يعيد توقيع الاتصالات بجهته الخاصة، والحل إضافة الشهادة الرئيسية لتلك الجهة إلى مخزن الثقة.
6. **مفتاح مشفر**: إذا كان المفتاح `BEGIN ENCRYPTED PRIVATE KEY` فسوف يطلب Nginx عبارة المرور ثم يفشل عند التشغيل في الخلفية برسالة طويلة تنتهي بـ `bad password read`، والحل فك تشفير المفتاح مرة واحدة بالأمر `openssl pkey -in server-enc.key -out server.key` وحمايته بالصلاحيات.
7. **الساعة غير مضبوطة**: `error 9 at 0 depth lookup: certificate is not yet valid` عندما تكون ساعة الجهاز متأخرة عن تاريخ بداية الشهادة، أو `certificate has expired` لشهادة صالحة عندما تكون الساعة متقدمة، وتراه في الأجهزة التي فقدت بطارية الساعة وفي الأجهزة الافتراضية بعد استعادة Snapshot قديم. افحص الساعة قبل أن تشك في الشهادة، وقد شرحنا ضبطها في قسم [مزامنة الساعة](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%A7%D8%B1%D9%8A%D8%AE-%D9%88%D8%A7%D9%84%D9%88%D9%82%D8%AA-utc-%D9%88%D8%A7%D9%84%D9%85%D9%86%D8%A7%D8%B7%D9%82-%D8%A7%D9%84%D8%B2%D9%85%D9%86%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%82%D9%88%D9%8A%D9%85-%D8%A7%D9%84%D9%87%D8%AC%D8%B1%D9%8A/#time-sync).

**ملف PFX قديم في OpenSSL 3**: عند فتح ملف صدر من نظام قديم بتشفير RC2 تظهر الرسالة التالية، والحل إضافة `-legacy` إلى أمر `openssl pkcs12`:

```text
Error outputting keys and certificates
40A73C3D107F0000:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:../crypto/evp/evp_fetch.c:376:Global default library context, Algorithm (RC2-40-CBC : 0), Properties ()
```

**صيغة خاطئة**: عندما وضعنا ملف DER مكان PEM في Nginx 1.31.6 ظهرت الرسالة التالية، و`no start line` معناها أن البرنامج بحث عن سطر `-----BEGIN` فلم يجده، فإما أن الملف DER أو PFX، أو أن فيه شيئاً آخر مثل مفتاح في مكان الشهادة، والحل التحويل بأمر `openssl x509 -inform DER -in file.cer -out file.pem`:

```text
nginx: [emerg] cannot load certificate "/etc/nginx/certs/fullchain.pem": PEM_read_bio_X509_AUX() failed (SSL: error:0480006C:PEM routines::no start line:Expecting: TRUSTED CERTIFICATE)
```

**المفتاح لا يطابق الشهادة**: في Nginx تظهر الرسالة التالية عند `nginx -t`، وفي HAProxy الرسالة [inconsistencies between private key and certificate](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-haproxy-load-balancer/#pem-order)، والحل مقارنة المفتاح العام كما في [الخطوة 8](#lab-key-match):

```text
nginx: [emerg] SSL_CTX_use_PrivateKey("/etc/nginx/certs/privkey.pem") failed (SSL: error:05800074:x509 certificate routines::key values mismatch)
```

## جدول الأوامر والصيغ

الجدول التالي يجمع الأوامر التي استخدمناها في المقال:

| المهمة                     | الأمر                                                                                          |                |
| -------------------------- | ---------------------------------------------------------------------------------------------- | -------------- |
| قراءة شهادة                | openssl x509 -in cert.pem -text -noout                                                         |                |
| الاسم والجهة والتواريخ فقط | openssl x509 -in cert.pem -noout -subject -issuer -dates -ext subjectAltName                   |                |
| شهادة موقع وسلسلته         | echo \| openssl s\_client -connect host:443 -servername host -showcerts                        |                |
| توليد مفتاح                | openssl genpkey -algorithm EC -pkeyopt ec\_paramgen\_curve:P-256 -out key.pem                  |                |
| طلب شهادة CSR              | openssl req -new -key key.pem -subj "/CN=host" -addext "subjectAltName=DNS:host" -out req.csr  |                |
| قراءة CSR                  | openssl req -in req.csr -noout -text -verify                                                   |                |
| التحقق من السلسلة          | openssl verify -CAfile root.pem -untrusted chain.pem cert.pem                                  |                |
| PEM إلى DER                | openssl x509 -in cert.pem -outform DER -out cert.der                                           |                |
| DER إلى PEM                | openssl x509 -inform DER -in cert.der -out cert.pem                                            |                |
| PKCS#8 إلى PKCS#1          | openssl pkey -in key.pem -traditional -out key-pkcs1.pem                                       |                |
| فك تشفير مفتاح             | openssl pkey -in enc.key -out key.pem                                                          |                |
| صنع PKCS#12                | openssl pkcs12 -export -inkey key.pem -in cert.pem -certfile chain.pem -out bundle.p12         |                |
| شهادة من PKCS#12           | openssl pkcs12 -in bundle.p12 -clcerts -nokeys -out cert.pem                                   |                |
| مفتاح من PKCS#12           | openssl pkcs12 -in bundle.p12 -nocerts -noenc -out key.pem                                     |                |
| شهادات PKCS#7 إلى PEM      | openssl pkcs7 -in chain.p7b -print\_certs -out chain.pem                                       |                |
| هل المفتاح للشهادة؟        | openssl x509 -in cert.pem -noout -pubkey \| openssl sha256 ثم openssl pkey -in key.pem -pubout | openssl sha256 |
| هل تنتهي خلال 30 يوماً؟    | openssl x509 -in cert.pem -noout -checkend 2592000                                             |                |
| محتوى مخزن Java            | keytool -list -v -keystore store.p12                                                           |                |

والجدول التالي يقارن الصيغ:

| الصيغة  | الترميز                  | ما تحمله                        | الامتداد المعتاد    | من يستخدمها                                        |
| ------- | ------------------------ | ------------------------------- | ------------------- | -------------------------------------------------- |
| PEM     | نص Base64 بين BEGIN وEND | أي شيء، حسب سطر BEGIN           | .pem .crt .key .csr | Linux، Nginx، Apache، HAProxy، Postfix، Kubernetes |
| DER     | ثنائي ASN.1              | شهادة واحدة أو مفتاح            | .der .cer           | Windows، Java، الأجهزة المدمجة                     |
| PKCS#1  | PEM أو DER               | مفتاح RSA فقط                   | .key                | البرامج والإصدارات القديمة                         |
| PKCS#8  | PEM أو DER               | مفتاح خاص من أي نوع، مشفر أو لا | .key .pem           | OpenSSL 3 افتراضياً، وأغلب البرامج الحديثة         |
| PKCS#7  | PEM أو DER               | شهادات وسلسلة، دون مفتاح        | .p7b .p7c           | Windows، بعض الجهات التجارية                       |
| PKCS#12 | ثنائي بكلمة مرور         | مفتاح وشهادة وسلسلة             | .pfx .p12           | IIS وWindows، Java، الجوال والأجهزة                |
| JKS     | ثنائي خاص بـ Java        | مفاتيح وشهادات                  | .jks                | تطبيقات Java القديمة                               |

## تدريب عملي

- اكتب اسم نطاقك في [crt.sh](https://crt.sh/?ref=arabroot.io)، وراجع كل الشهادات الصادرة له، وتأكد أنك تعرف من طلب كل واحدة منها.
- نفذ على كل نطاق لديك الأمر `echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep -A4 "Certificate chain"`، وتأكد أن السلسلة فيها شهادتان على الأقل.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- التشفير دون التحقق من الهوية لا يحمي من المهاجم في منتصف الطريق، والشهادة تربط اسم النطاق بمفتاحه العام بتوقيع جهة إصدار يثق بها جهازك.
- الشهادة X.509 بيانات عامة: Subject وSAN وIssuer وValidity والمفتاح العام والاستخدامات، والذي يثبت أن السيرفر صاحبها هو توقيعه بالمفتاح الخاص في المصافحة.
- السلسلة تبدأ بشهادة النطاق ثم الوسيطة وتنتهي بشهادة رئيسية في مخزن الثقة، ويجب أن يرسل السيرفر الوسيطة دائماً، أي `fullchain.pem` وليس `cert.pem`.
- مدة الشهادات العامة تقصر إلى 47 يوماً في مارس 2029، وOCSP في طريقه إلى الزوال، فاجعل التجديد آلياً بالكامل وراقبه، وراقب نطاقك في سجلات الشفافية وقيده بسجل CAA.
- PEM وDER طريقتان لكتابة البايتات نفسها، وPKCS#1 وPKCS#8 للمفاتيح، وPKCS#7 للسلاسل، وPKCS#12 للحزمة الكاملة بكلمة مرور، والامتداد لا يحدد شيئاً، فافتح الملف واقرأ سطر BEGIN.
- أغلب الأخطاء سلسلة ناقصة، أو اسم لا يطابق، أو شهادة منتهية، أو مفتاح لا يطابق، أو صيغة خاطئة، وكل واحد منها له رسالة تقرؤها وأمر يكشفه في دقيقة.

[![](https://arabroot.io/content/images/2026/10/encodings-cover-cover.webp)دليلالترميز Encoding للمطورين: Base64 وأخواتهلماذا نحول البايتات إلى حروف حتى تمر في البريد والروابط، وكيف تفك أي سلسلة منها، ولماذا لا يحمي Base64 أي سر.](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-base64-%D9%88-base32-%D9%88-base58/)[![](https://arabroot.io/content/images/2026/10/security-headers-cover-cover.webp)دليلHSTS وCSP: ترويسات الأمان التي يحتاجها كل تطبيقما الذي تطلبه كل ترويسة أمان من المتصفح، ولماذا يصعب التراجع عن HSTS، وكيف تبني سياسة CSP لا تكسر تطبيقك.](https://arabroot.io/articles/%D8%AA%D8%B1%D9%88%D9%8A%D8%B3%D8%A7%D8%AA-%D8%A7%D9%84%D8%A3%D9%85%D8%A7%D9%86-hsts-%D9%88-csp/)[![](https://arabroot.io/content/images/2026/10/nginx-proxy-manager-cover-cover-1.webp)دليلNginx Proxy Manager: نطاقات وشهادات TLS لكل خدماتككيف تربط كل خدمة على سيرفرك بنطاقها وتمنحها شهادة HTTPS مجانية من واجهة بسيطة، دون أن تكتب إعدادات Nginx بنفسك.](https://arabroot.io/articles/nginx-proxy-manager-%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-tls/)[![](https://arabroot.io/content/images/2026/10/iam-pam-cover-cover.webp)دليلإدارة الهوية والوصول IAM والوصول المميز PAMمن يملك صلاحية root على سيرفراتك ومنذ متى، وكيف تضبط حسابات المديرين بصلاحيات مؤقتة وجلسات مسجلة.](https://arabroot.io/articles/%D8%A7%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D9%87%D9%88%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D9%88%D8%B5%D9%88%D9%84-iam-%D9%88%D8%A7%D9%84%D9%88%D8%B5%D9%88%D9%84-%D8%A7%D9%84%D9%85%D9%85%D9%8A%D8%B2-pam/)[![](https://arabroot.io/content/images/2026/10/dns-records-cover-cover.webp)دليلشرح DNS وسجلاته للمبتدئين بمثال حقيقيكيف يتحول اسم نطاقك إلى عنوان سيرفرك، وما وظيفة كل سجل تضيفه في لوحة النطاق، والأخطاء التي تضيع فيها الساعات عادة.](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-dns-%D9%88%D8%B3%D8%AC%D9%84%D8%A7%D8%AA%D9%87-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، وتنفيذ الأمثلة على Debian 13.7 مع OpenSSL 3.5.7 وcurl 8.14.1، وNginx 1.31.6، وkeytool من Eclipse Temurin 25.0.4.1، وقراءة شهادة github.com كما كانت في أكتوبر 2026.
- أكتوبر 2026: إضافة رابط في قسم أنواع الشهادات إلى مقال إشارة SNI والشهادة متعددة الأسماء SAN.