التحقق من شهادات SSL في Go: دليل شامل للمطورين
في الآونة الأخيرة، لفت انتباهي منتج SaaS يقدم خدمة التحقق من شهادات SSL للمواقع الإلكترونية. دفعني الفضول لتجربة بناء وظيفة مماثلة باستخدام لغة Go، وقد تبين أن العملية بسيطة ومباشرة بشكل مدهش، ولا تتطلب سوى بضعة أسطر من التعليمات البرمجية. لضمان التحقق الشامل، ستحتاج إلى إجراء ثلاثة فحوصات رئيسية على موقعك:
- التحقق من وجود شهادة
SSLوصلاحيتها: التأكد مما إذا كان الموقع يستخدم شهادةSSLمن الأساس، وما إذا كانت هذه الشهادة موقعة من قبل سلطة إصدار شهادات (CA) موثوقة وليست ذاتية التوقيع (self-signed)، حيث أن الشهادات ذاتية التوقيع لا تُعتبر صالحة في بيئات الإنتاج. - مطابقة اسم المضيف (
Hostname): التأكد من أن اسم المضيف المحدد في الشهادة يتطابق مع النطاق الفعلي للموقع. - تاريخ انتهاء صلاحية الشهادة: معرفة متى ستنتهي صلاحية شهادة الخادم لتجنب أي انقطاعات محتملة في الخدمة.
المتطلبات الأساسية
للبدء في هذا الدليل، يجب أن يكون لديك بيئة عمل Go مثبتة ومُعدة بشكل صحيح على جهاز الكمبيوتر الخاص بك. يمكنك زيارة الموقع الرسمي للغة Go للحصول على تعليمات التثبيت.
الخطوة الأولى: التحقق من وجود شهادة SSL
تتمثل الخطوة الأولى في تحديد ما إذا كان الموقع الإلكتروني يمتلك شهادة SSL/TLS فعالة أم لا. لتحقيق ذلك، سنحاول إنشاء اتصال TLS آمن مع الموقع المستهدف. إذا نجح هذا الاتصال، فهذا يشير إلى أن الموقع يستخدم شهادة TLS صالحة. يمكننا الاستفادة من حزمة crypto/tls المدمجة في Go لإجراء هذه العملية.
سنستخدم الدالة Dial من هذه الحزمة لإنشاء الاتصال، كما هو موضح في المثال التالي:
package main
import (
"crypto/tls"
)
func main() {
conn, err := tls.Dial("tcp", "example.com:80", nil)
if err != nil {
panic("Server doesn't support SSL certificate err: " + err.Error())
}
}
سنقوم بتجربة هذا الكود على النطاق example.com. عند تشغيل الكود أعلاه، من المتوقع أن تحصل على الخطأ التالي، والذي يشير إلى أن الموقع لا يدعم SSL على المنفذ 80:
$ go run main.go
panic: Server doesn't support SSL certificate err: tls: first record does not look like a TLS handshake
goroutine 1 [running]:
main.main()
/Users/umesh/personal/spike/main.go:99 +0x2ca
exit status 2
بشكل أساسي، حاول الكود إنشاء اتصال آمن ولكنه فشل. تنجح الدالة Dial فقط عندما يكون الموقع مزودًا بشهادة SSL صالحة وموقعة من جهة موثوقة. ستفشل هذه الدالة إذا كانت الشهادة ذاتية التوقيع (self-signed) أو غير موجودة.
الآن، لنحاول تشغيل نفس الكود على موقع يدعم SSL بشكل كامل. في هذا المثال، سنستخدم عنوان URL لموقع شخصي:
package main
import (
"crypto/tls"
)
func main() {
conn, err := tls.Dial("tcp", "blog.umesh.wtf:443", nil)
if err != nil {
panic("Server doesn't support SSL certificate err: " + err.Error())
}
}
في هذه الحالة، يجب أن نتمكن من إنشاء الاتصال بنجاح دون حدوث أي خطأ من نوع panic. من المهم الإشارة إلى أنه في بيئات الإنتاج، يجب عليك تجنب استخدام panic والتعامل مع الأخطاء بشكل أكثر احترافية ومرونة.
الخطوة الثانية: مطابقة اسم المضيف (Hostname) مع شهادة SSL
بعد التأكد من وجود شهادة SSL، تأتي خطوة حاسمة وهي التحقق من أن اسم المضيف الذي يحاول المستخدم الوصول إليه يتطابق مع الاسم المحدد في الشهادة. هذا يمنع هجمات انتحال الشخصية (man-in-the-middle attacks) ويضمن أن المستخدم يتصل بالخادم الصحيح.
لتحقيق ذلك، سنستدعي الدالة VerifyHostname على كائن الاتصال conn الذي تم إرجاعه بواسطة الدالة Dial. تحاول هذه الدالة مطابقة الاسم الشائع (common name) أو الأسماء البديلة للموضوع (subject alt name) المحددة في الشهادة مع النطاق الذي تم تمريره كمعامل.
package main
import (
"crypto/tls"
)
func main() {
conn, err := tls.Dial("tcp", "blog.umesh.wtf:443", nil)
if err != nil {
panic("Server doesn't support SSL certificate err: " + err.Error())
}
err = conn.VerifyHostname("blog.umesh.wtf")
if err != nil {
panic("Hostname doesn't match with certificate: " + err.Error())
}
}
عند تشغيل هذا الكود، سيتم تنفيذه بنجاح دون أي أخطاء، وذلك لأن الاسم الشائع في الشهادة واسم المضيف الذي نتحقق منه متطابقان في هذا المثال.
الخطوة الثالثة: التحقق من تاريخ انتهاء صلاحية شهادة SSL
تُعد مراقبة تاريخ انتهاء صلاحية شهادة SSL أمرًا حيويًا لتجنب انقطاع الخدمة أو ظهور تحذيرات أمنية للمستخدمين. يمكننا الحصول على سلسلة الشهادات (certificate chain) باستخدام الدالة conn.ConnectionState().PeerCertificates.
بعد الحصول على قائمة الشهادات، يمكننا استخراج تاريخ انتهاء الصلاحية من الشهادة الأولى في السلسلة (والتي عادةً ما تكون شهادة الخادم النهائية). نستخدم الحقل NotAfter للوصول إلى هذا التاريخ.
package main
import (
"crypto/tls"
"fmt"
"time"
)
func main() {
conn, err := tls.Dial("tcp", "blog.umesh.wtf:443", nil)
if err != nil {
panic("Server doesn't support SSL certificate err: " + err.Error())
}
err = conn.VerifyHostname("blog.umesh.wtf")
if err != nil {
panic("Hostname doesn't match with certificate: " + err.Error())
}
expiry := conn.ConnectionState().PeerCertificates[0].NotAfter
fmt.Printf("Issuer: %s\nExpiry: %v\n", conn.ConnectionState().PeerCertificates[0].Issuer, expiry.Format(time.RFC850))
}
عند تشغيل الكود أعلاه، يجب أن يحتوي الإخراج على اسم جهة الإصدار (Issuer Name) وتاريخ انتهاء صلاحية الشهادة، كما هو موضح أدناه:
$ go run main.go
Issuer : CN=Let's Encrypt Authority X3, O=Let's Encrypt, C=US
Expiry : Wednesday, 16-Dec-20 16:20:00 UTC
بهذه الخطوات، نكون قد نجحنا في التحقق من صحة شهادة SSL للموقع بشكل شامل.
الخلاصة التقنية
لقد استعرضنا في هذا المقال كيفية التحقق الأساسي من شهادات SSL باستخدام لغة Go وحزمتها القياسية crypto/tls. يوضح هذا الدليل أن Go توفر أدوات قوية ومباشرة للتعامل مع مهام أمان الشبكات دون الحاجة إلى مكتبات خارجية معقدة. من خلال فحص وجود الشهادة، مطابقة اسم المضيف، وتاريخ الانتهاء، يمكن للمطورين بناء أنظمة موثوقة تتحقق من صحة الاتصالات الآمنة. هذه القدرة أساسية لأي تطبيق يتطلب اتصالات مشفرة، وتُعد معرفتها مهارة لا غنى عنها في عالم تطوير الويب الحديث.
بالإضافة إلى ما تم شرحه، يمكن للمطورين استكشاف المزيد من التفاصيل المتاحة ضمن كائن ConnectionState، مثل الحصول على معلومات حول الشهادة الجذرية (root CA)، تاريخ إصدار الشهادة، وجميع الشهادات المتسلسلة (chained certificates). هذه المرونة تجعل Go خيارًا ممتازًا لبناء أدوات مخصصة للتحقق من أمان TLS.