أبقِ سلطة الشهادة الجذرية لشبكتك الخاصة الافتراضية دون اتصال وأصدر الشهادات مع ذلك
أبقِ مفتاح الجذر بعيدًا عن الشبكة، ووقّع الشهادات الطرفية بواسطة سلطة شهادة وسيطة، وانشر قائمة إبطال الشهادات في وقتها، واختبر السلسلة قبل اتصال أول عميل.
تستجيب شبكتك الخاصة الافتراضية للاتصالات في الساعة 3 صباحًا بينما يبقى الجهاز الذي يوقّع شهاداتها مطفأً في درج. لا يتصل أي عميل يتحقق من شهادة الخادم بسلطة الشهادة الخاصة بك أبدًا، لذا لا تحتاج سلطة الشهادة إلى أن تكون قابلة للوصول لأي اتصال. الجزء الذي يحتاج إلى تخطيط هو اليوم الذي يجب فيه إصدار شهادة أو تجديدها أو إبطالها بينما يبقى مفتاح الجذر محفوظًا في مكان مقفل.
ما الذي يتحقق منه الاتصال فعليًا
عندما يتصل عميل OpenVPN، فإنه يسير في سلسلة التوقيع من شهادة الخادم الطرفية صعودًا إلى شهادة سلطة شهادة يثق بها مسبقًا، ويتحقق من نافذة الصلاحية، ويستشير قائمة إبطال إذا نصّ الملف الشخصي على ذلك. ولا يبحث أبدًا عن سلطة الشهادة الجذرية الخاصة بك. ويقوم الخادم بالعملية المعاكسة لشهادات العملاء. وعند وقت الاتصال لا يكون حاضرًا سوى الشهادة الطرفية والشهادة الوسيطة وحزمة الثقة التي وزّعتها سابقًا.
ما يفشل فعليًا هو أحد ثلاثة أشياء: أن يكون مفتاح التوقيع غير متاح عند الإصدار، أو أن تصبح قائمة الإبطال قديمة، أو أن تكون حزمة ثقة العميل أقدم من السلسلة التي يقدمها الخادم. الأول مشكلة في العملية؛ والآخران مشكلتان في التوزيع. ولا يتطلب أي منها أن يكون مفتاح الجذر متصلًا، وخدمة VPN المُدارة التي تطالب بشهادة خاصة لمصادقة النفق تنقل سلطة التوقيع إلى المزوّد فقط. أما نقطة Azure إلى الموقع فتعمل بالعكس: ترفع شهادة جذرية فتُوثق الشهادات الموقّعة تحتها، فاسأل من يوقّع، ومن يتحقق، وكيف تُسحب شهادة مبكرًا.
ابنِ الجذر على مضيف لا يحصل على عنوان أبدًا
نادرًا ما يُستخدم الجذر غير المتصل: لتوقيع شهادة سلطة الشهادة الوسيطة، ولاحقًا لتجديدها أو إبطالها. كل شيء آخر يحدث على مستوى أدنى. أنجز عمل الجذر على جهاز يُبقى خارج الشبكة، من نظام حي إن أمكن، وسجّل من كان في الغرفة. تفترض الأوامر OpenSSL 1.1.1 أو أحدث، لأن -addext غير متاح في الإصدارات الأقدم.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out root.key
openssl req -x509 -new -key root.key -sha256 -days 7300 \
-subj "/CN=Example VPN Root CA" -out root.crt \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
أنشئ الشهادة الوسيطة على مضيف التوقيع واحمل طلبها فقط إلى الجهاز غير المتصل. يوقّع الجذر ذلك الطلب بطول مسار صفر، فيجوز للشهادة الوسيطة توقيع شهادات الأطراف لكنها لا يجوز أن تنشئ سلطة شهادة أخرى تحتها. هذا الامتداد الواحد يمنع مضيف توقيع مخترقًا من إصدار سلطة مكافئة للجذر. لا تستخدم ECDSA إلا إذا كان كل عميل يشغّل OpenVPN 2.4 أو أحدث؛ وإلا فأنشئ مفاتيح RSA وأبقِ المسار كما هو.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out intermediate.key
openssl req -new -key intermediate.key -subj "/CN=Example VPN Intermediate CA" -out intermediate.csr
openssl x509 -req -in intermediate.csr -CA root.crt -CAkey root.key \
-CAcreateserial -days 3650 -sha256 -extfile intermediate.ext -out intermediate.crt
تحتوي intermediate.ext على basicConstraints=critical,CA:TRUE,pathlen:0 و keyUsage=critical,keyCertSign,cRLSign و authorityKeyIdentifier=keyid:always. أعِد مفتاح الجذر إلى خزانته قبل إعادة توصيل مضيف التوقيع. إذا كان الجذر يجب أن يستطيع إبطال الشهادة الوسيطة، فوقّعها باستخدام openssl ca -config root.cnf -extensions intermediate_ca مقابل index.txt على مستوى الجذر بدلًا من openssl x509 -req، حتى يُسجَّل الرقم التسلسلي؛ ثم أرسل قائمة الجذر الناتجة داخل ملف العميل الشخصي، حيث تقرأها أداة crl-verify المعتمدة على ملف عند بدء التشغيل. أرسل للعملاء حزمة تحمل شهادتي سلطة الشهادة؛ فالشهادة الطرفية وحدها لن تتحقق مقابل الجذر.
أصدر الشهادات الطرفية من الوسيطة، مع قاعدة بيانات
التوقيع باستخدام openssl x509 -req يعمل إلى أن تحتاج إلى الإبطال، لأن شيئًا لا يسجّل الرقم التسلسلي. استخدم openssl ca من أول شهادة حتى تعيش الأرقام التسلسلية وتواريخ الانتهاء وحالة الإبطال في قاعدة بيانات واحدة. أنشئ ملفات قاعدة البيانات مرة واحدة، ثم وجّه الإعدادات إليها:
mkdir -p /etc/vpn-ca/intermediate/newcerts
touch /etc/vpn-ca/intermediate/index.txt
echo 1000 > /etc/vpn-ca/intermediate/serial
echo 1000 > /etc/vpn-ca/intermediate/crlnumber
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = /etc/vpn-ca/intermediate
database = $dir/index.txt
new_certs_dir = $dir/newcerts
serial = $dir/serial
crlnumber = $dir/crlnumber
certificate = $dir/intermediate.crt
private_key = $dir/intermediate.key
default_md = sha256
default_days = 365
default_crl_days = 30
unique_subject = no
policy = policy_any
[ policy_any ]
commonName = supplied
[ server_cert ]
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature
extendedKeyUsage = serverAuth
subjectAltName = @server_names
[ client_cert ]
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature
extendedKeyUsage = clientAuth
[ server_names ]
DNS.1 = vpn.example.com
يفترض سطر استخدام المفتاح مفتاح ECDSA؛ أضف keyEncipherment من أجل RSA. امنح كل عميل اسمًا شائعًا خاصًا به، لأن OpenVPN يتعرّف على الاتصال بهذا الحقل، ولا يمكن التمييز بين حاسوبين محمولين يتشاركان CN=vpn-client في السجلات، أو في قواعد client-config-dir، أو في أي دفع خاص بمستخدم، وتفقد الإسناد الذي يجعل قرار الإبطال قابلًا للدفاع. أصدر شهادة طرفية وتحقق منها هكذا:
openssl req -new -key alice.key -subj "/CN=alice-laptop" -out alice.csr
openssl ca -config intermediate.cnf -extensions client_cert -in alice.csr -out alice.crt -batch
openssl verify -CAfile ca-bundle.crt -untrusted intermediate.crt alice.crt
أبقِ مفتاح التوقيع خلف حدود سياسة بدلًا من نسخه إلى مضيف VPN: خدمة صغيرة تصادق على الطلب، وتتحقق من الاسم الشائع مقابل قائمة مسموح بها، وتعيد الشهادة فقط. سجّل كل رقم تسلسلي؛ السجل هو الرابط الوحيد بين الشهادة والشخص الذي طلبها.
انشر الإبطال دون إيقاظ الجذر
تُبطل الشهادات الوسيطة شهادات العملاء، وليس الجذر أبدًا. قائمة الجذر لا تهم إلا عندما تُحرق شهادة وسيطة. وجّه الخادم إلى القائمة الوسيطة باستخدام crl-verify. تُحمَّل قائمة الإبطال المعتمدة على ملف عند بدء الخدمة، فأعد التحميل بعد كل إعادة توليد؛ أما صيغة الدليل فتقرأ الحالة عند وقت الاتصال وتكلف ملفًا واحدًا لكل رقم تسلسلي.
أبطل وأعد التوليد بأمرين من قاعدة البيانات نفسها:
openssl ca -config intermediate.cnf -revoke /etc/vpn-ca/intermediate/newcerts/1001.pem
openssl ca -config intermediate.cnf -gencrl -out /etc/openvpn/crl.pem
ثم راقب الساعة. تحمل قائمة الإبطال حقل التحديث التالي، وبمجرد أن يمر ذلك التاريخ يرفض نظير يقوم بالتحقق القائمة بدلًا من تجاهلها. إعادة التوليد شهريًا باستخدام default_crl_days = 30 تترك هامشًا؛ أما سنويًا فلا. العمر الأقصر للشهادات الطرفية هو الضابط الأكثر اعتمادًا، لأن الإبطال لا ينطبق إلا حيث تُستشار القائمة.
اختبر السلسلة، ثم دوّن المراسم
أعِد إنتاج أربعة إخفاقات قبل أن تثق بالتصميم. تحقق من السلسلة باستخدام openssl verify -CAfile ca-bundle.crt -untrusted intermediate.crt server.crt. اتصل بشهادة عميل منتهية وتأكد من أن الخادم يرفضها. أبطل شهادة عميل اختباري، وأعد التحميل، وتأكد من أن القائمة تدخل حيز التنفيذ. قدّم ساعة أحد المضيفين يومًا للأمام، لأن ساعة خاطئة قد تصدر شهادات وترفض أخرى تبدو صالحة في مكان آخر.
لا يكون الجذر غير المتصل ضابطًا إلا إذا وُجدت الأوراق: شخصان مسمّيان للمراسم، وسجل لكل رقم تسلسلي موقّع، ونسخة احتياطية مشفّرة مخزّنة بعيدًا عن الجهاز، واختبار استعادة على مضيف معزول. وقّع شهادة وسيطة احتياطية خلال المراسم نفسها حتى لا يضطرك مضيف توقيع معطّل إلى فتح الجذر في منتصف الليل.
قبل أن تعتبره منتهيًا، تأكد من خمسة أشياء: أن مفتاح الجذر ليس على مضيف متصل بالشبكة، وأن مفتاح الوسيطة لا يعيش إلا على مضيف التوقيع، وأن حزمة العميل تحمل شهادتي سلطة الشهادة، وأن قائمة الإبطال لديها تحديث تالٍ في المستقبل، وأن شخصًا ما يملك إدخال تقويم لكل تاريخ انتهاء في السلسلة. وإن غاب أحدها، فالإعداد الذي يبدو غير متصل هو مجرد إعداد غير موثّق.