رفتن به محتوا
مبانی کامپیوتر و اینترنتسرور، ابر و امنیت پایهدرس ۱۵ از ۱۶

HTTPS و گواهی، ساده

قفل کنار آدرس چه چیزی را تضمین می‌کند و چه چیزی را نه: سه وعده‌ی HTTPS (محرمانگی، درستی، هویت)، TLS و دست‌دادنش، گواهی دیجیتال و فیلدهایش، مرجع صدور (CA) و زنجیره‌ی اعتماد، ساختن یک گواهی خودامضا و یک CA آزمایشی با openssl، خطاهای self-signed و نام نادرست، دیدن چیزهایی که حتی با HTTPS لو می‌رود (اسم سایت)، و Let's Encrypt.

  • حدود ۲۸ دقیقه مطالعه
  • ۷ مثال اجراشده
  • ۳ تمرین با جواب
  • مقدماتی (از صفر مطلق)
  • به‌روزرسانی: ۲۹ مهر ۱۴۰۵

توی این درس یاد می‌گیری قفل کوچک کنار آدرس سایت دقیقاً چه معنایی دارد. HTTPS همان HTTP درس قبل است که داخل یک «تونل» رمزنگاری‌شده به اسم TLS حرکت می‌کند. سه چیزی را که HTTPS تضمین می‌کند (و چیزهایی را که تضمین نمی‌کند) می‌شناسی، با گواهی دیجیتال و مرجع صدور (CA) آشنا می‌شوی، خودت یک گواهی می‌سازی و یک سرور HTTPS روی کامپیوترت بالا می‌آوری، خطاهای معروف مرورگر را با چشم خودت می‌سازی و می‌فهمی، و در آخر یک CA کوچک آزمایشی درست می‌کنی تا ببینی «اعتماد» در اینترنت چطور کار می‌کند.

مسئله: HTTP مثل کارت‌پستال است

Section titled “مسئله: HTTP مثل کارت‌پستال است”

در درس HTTP دیدی همه‌چیز متن ساده است: آدرس، کوکی‌ها، فرم‌ها، رمزی که تایپ می‌کنی. هر کس در مسیر (صاحب Wi-Fi کافه، هر روتری در راه، یا هر کسی که توانسته بین تو و سرور قرار بگیرد) همه را می‌خواند، و بدتر، می‌تواند عوض کند: یک تبلیغ در صفحه بگذارد، شماره‌حساب را عوض کند، یا خودش را جای سایت جا بزند. HTTPS برای این سه مشکل است.

تشبیه: پاکت مهروموم‌شده با کارت شناسایی

Section titled “تشبیه: پاکت مهروموم‌شده با کارت شناسایی”

HTTP یعنی کارت‌پستال: هر کس در مسیر می‌خواندش و می‌تواند رویش بنویسد. HTTPS یعنی:

  1. پاکت دربسته (محرمانگی): نامه داخل پاکتی است که فقط گیرنده می‌تواند باز کند.
  2. مهروموم (درستی): اگر کسی در راه پاکت را باز کند یا چیزی عوض کند، مهر می‌شکند و گیرنده می‌فهمد.
  3. کارت شناسایی (هویت): قبل از تحویل، طرف مقابل یک کارت شناسایی (گواهی) نشان می‌دهد که یک اداره‌ی معتبر (CA) صادر کرده و رویش نوشته «این واقعاً بانک ملت است». بدون این، ممکن است پاکت را با خیال راحت دست یک کلاهبردار بدهی؛ رمزنگاری کامل، با آدم اشتباه.

سومی را خیلی‌ها فراموش می‌کنند و مهم‌ترینش است.

HTTPS همان HTTP است که داخل یک تونل TLS حرکت می‌کند؛ هر کس در مسیر فقط بایت‌های درهم و اسم سایت را می‌بیند.

TLS (Transport Layer Security؛ نسخه‌ی قدیمی‌اش SSL نام داشت و هنوز این اسم را زیاد می‌شنوی) قبل از اینکه حتی یک بایت HTTP رد و بدل شود، یک دست‌دادن (handshake) انجام می‌دهد:

دست‌دادن TLS 1.3 (ساده‌شده): بعد از یک رفت‌وبرگشت، هر دو طرف کلید متقارن مشترک دارند و مرورگر هویت سرور را بررسی کرده است.

ایده‌های درس رمز و هش این‌جا کنار هم می‌نشینند: سرور با کلید خصوصی‌اش امضا می‌کند تا ثابت کند صاحب گواهی است؛ دو طرف با یک روش ریاضی (Diffie-Hellman) یک کلید متقارن مشترک می‌سازند بدون اینکه خود کلید از شبکه رد شود؛ و بقیه‌ی گفت‌وگو با رمزنگاری متقارن سریع (مثل AES) انجام می‌شود. هر پیام هم یک «مهر» (کد اعتبارسنجی) دارد که هر تغییری را لو می‌دهد.

گواهی (certificate) یک فایل کوچک است که می‌گوید: «کلید عمومی ‌‌X مال سایت shop.example.com است، از فلان تاریخ تا فلان تاریخ، و من، مرجع Y، این را امضا می‌کنم». فیلدهای اصلی:

فیلد معنی
Subject / SAN گواهی برای کدام اسم‌ها (Subject Alternative Name: فهرست دامنه‌ها)
Issuer چه کسی صادرش کرده (CA)
Validity از کی تا کی معتبر است
Public key کلید عمومی سرور
Signature امضای CA روی همه‌ی موارد بالا

مرورگر از کجا به CA اعتماد می‌کند؟ سیستم‌عامل و مرورگر یک فهرست از پیش نصب‌شده از CAهای مورد اعتماد (گواهی‌های ریشه) دارند. هر گواهی سایت باید با یک زنجیره از امضاها به یکی از این ریشه‌ها برسد.

همه‌ی مثال‌ها در پوشه‌ی ~/tls و روی کامپیوتر خودت انجام می‌شوند:

Terminal window
mkdir -p ~/tls && cd ~/tls

مثال ۱: یک گواهی خودامضا

Section titled “مثال ۱: یک گواهی خودامضا”

خودامضا (self-signed) یعنی گواهی‌ای که هیچ CAی امضایش نکرده؛ خودش، خودش را تأیید کرده. مثل کارت شناسایی که خودت در خانه چاپ کرده‌ای. برای آزمایش خوب است:

Terminal window
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-keyout server.key -out server.crt -days 30 \
-subj "/CN=localhost" -addext "subjectAltName=DNS:localhost"
ls -l
خروجی
-----
total 8
-rw-rw-r-- 1 ali ali 607 Oct 22 00:33 server.crt
-rw------- 1 ali ali 241 Oct 22 00:33 server.key

یک دستور بلند، ولی هر بخشش ساده است: req -x509 (یک گواهی بساز)، -newkey ec ... (یک جفت کلید جدید از نوع منحنی بیضوی، کوتاه و سریع)، -nodes (کلید خصوصی را با رمز قفل نکن، برای اینکه سرور بتواند بدون پرسیدن بخواندش)، ۳۰ روز اعتبار، برای اسم localhost. (\ آخر خط یعنی «دستور در خط بعد ادامه دارد».) دو فایل ساخته شد:

  • server.crt گواهی، شامل کلید عمومی: آزادانه به همه داده می‌شود.
  • server.key کلید خصوصی: مجوزش -rw------- است (فقط صاحبش می‌خواند؛ دوره‌ی لینوکس، درس مجوزها). کسی که این را داشته باشد، می‌تواند خودش را جای سایت تو جا بزند.

داخل گواهی را بخوان:

Terminal window
openssl x509 -in server.crt -noout -subject -issuer -dates -ext subjectAltName
خروجی
subject=CN = localhost
issuer=CN = localhost
notBefore=Oct 21 21:03:40 2026 GMT
notAfter=Nov 20 21:03:40 2026 GMT
X509v3 Subject Alternative Name:
DNS:localhost

subject و issuer یکی‌اند: همان «خودامضا». notBefore و notAfter بازه‌ی اعتبار (به وقت گرینویچ). فایل‌ها خودشان متن‌اند (head -3 server.crt را امتحان کن): -----BEGIN CERTIFICATE----- و بعد بایت‌ها با Base64 (درس قبل).

همان وب‌سرور پایتون درس کلاینت و سرور، با چند خط اضافه برای TLS. فایل ~/tls/https_server.py:

https_server.py
import http.server, ssl
server = http.server.HTTPServer(("127.0.0.1", 8443), http.server.SimpleHTTPRequestHandler)
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.crt", "server.key")
server.socket = ctx.wrap_socket(server.socket, server_side=True)
print("serving https://localhost:8443 ...")
server.serve_forever()
Terminal window
echo "<h1>صفحه‌ی امن</h1>" > index.html
python3 https_server.py

در ترمینال دیگر (و در همان پوشه):

Terminal window
cd ~/tls
curl -sS https://localhost:8443/
echo "exit=$?"
خروجی
curl: (60) SSL certificate problem: self-signed 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 web page mentioned above.
exit=60

curl قبول نکرد. ارتباط را می‌شد رمزنگاری کرد، ولی هیچ CAی تأیید نکرده که این سرور واقعاً «localhost» است. مرورگر هم همین را با صفحه‌ی قرمز «Your connection is not private» (کد NET::ERR_CERT_AUTHORITY_INVALID) نشان می‌دهد. این دقیقاً همان کاری است که باید بکند: بدون هویت، رمزنگاری بی‌معنی است.

مثال ۳: دو راه برای «قبول کردن»

Section titled “مثال ۳: دو راه برای «قبول کردن»”

راه غلط (ولی رایج در آزمایش): به curl بگو بررسی نکند:

Terminal window
curl -sSk https://localhost:8443/
خروجی
<h1>صفحه‌ی امن</h1>

-k (--insecure) یعنی «هر گواهی‌ای قبول است». ارتباط رمزنگاری‌شده است، ولی ممکن است با هر کسی باشد؛ پاکت دربسته، تحویل به یک غریبه. برای آزمایش روی کامپیوتر خودت اشکالی ندارد؛ در اسکریپت‌ها و برنامه‌های واقعی هرگز.

راه درست: به curl بگو دقیقاً به این گواهی اعتماد کند:

Terminal window
curl -sv --cacert server.crt https://localhost:8443/
خروجی (خط‌های مهم)
* Connected to localhost (127.0.0.1) port 8443
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / id-ecPublicKey
* Server certificate:
* subject: CN=localhost
* start date: Oct 21 21:03:40 2026 GMT
* expire date: Nov 20 21:03:40 2026 GMT
* subjectAltName: host "localhost" matched cert's "localhost"
* issuer: CN=localhost
* SSL certificate verify ok.
> GET / HTTP/1.1
> Host: localhost:8443
...
< HTTP/1.0 200 OK
...
<h1>صفحه‌ی امن</h1>

حالا دست‌دادن TLS را خط به خط می‌بینی، همان نمودار بالا: Client hello، Server hello، Certificate (سرور گواهی‌اش را فرستاد)، CERT verify (با کلید خصوصی امضا کرد که صاحبش است)، Finished. خط SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 می‌گوید: نسخه‌ی TLS 1.3، رمزنگاری متقارن AES با کلید ۲۵۶ بیتی، و توافق کلید با روش X25519. بعد بررسی هویت: اسم جور است (host "localhost" matched) و SSL certificate verify ok. فقط بعد از همه‌ی این‌ها، همان درخواست HTTP درس قبل (GET / HTTP/1.1) داخل تونل رفت.

گواهی برای localhost است. با آدرس دیگری از همان سرور وصل شو:

Terminal window
curl -sS --cacert server.crt https://127.0.0.1:8443/
خروجی
curl: (60) SSL: no alternative certificate subject name matches target host name '127.0.0.1'
More details here: https://curl.se/docs/sslcerts.html
...

همان سرور، همان گواهی، ولی گواهی می‌گوید «من مال localhost هستم» و تو 127.0.0.1 خواسته‌ای. curl رد کرد. در مرورگر: NET::ERR_CERT_COMMON_NAME_INVALID. این بررسی چیزی است که جلوی جا زدن را می‌گیرد: مهاجمی که برای evil.com گواهی معتبر گرفته، نمی‌تواند با آن خودش را bank.ir جا بزند. رایج‌ترین دلیل این خطا در دنیای واقعی: گواهی برای example.com گرفته‌ای و کاربر به www.example.com رفته (یا برعکس)؛ گواهی باید همه‌ی اسم‌ها را در SAN داشته باشد.

⚡ بررسی سریع

مرورگر برای سایت شرکت خطای «certificate is not valid for www.shop.ir» می‌دهد، ولی shop.ir درست باز می‌شود. مشکل چیست؟

مثال ۵: چه چیزی با HTTPS هم لو می‌رود؟

Section titled “مثال ۵: چه چیزی با HTTPS هم لو می‌رود؟”

با nc (درس پورت) خودت را جای سرور بگذار و ببین مرورگر/curl قبل از رمزنگاری چه می‌فرستد. در ترمینال سرور (اول سرور HTTPS را با Ctrl+C ببند):

Terminal window
nc -l 127.0.0.1 8444 > hello.bin

و در ترمینال دیگر یک درخواست HTTPS با یک کوکی و یک «شماره‌کارت» در آدرس:

Terminal window
curl -sk -m 1 -H "Cookie: session=abc123" "https://localhost:8444/orders?card=6037991234567890"

بعد ببین چه رسیده (strings متن‌های قابل‌خواندن داخل یک فایل دودویی را پیدا می‌کند):

Terminal window
ls -l hello.bin
strings -n 5 hello.bin
grep -c card hello.bin
خروجی
-rw-rw-r-- 1 ali ali 517 Oct 22 00:34 hello.bin
localhost
http/1.1
0

۵۱۷ بایت رسید: پیام Client hello. داخلش فقط دو چیز خواناست: اسم سایت (localhost) و پروتکلی که می‌خواهد (http/1.1). نه مسیر، نه card، نه کوکی؛ این‌ها فقط بعد از دست‌دادن و داخل تونل فرستاده می‌شوند. اسم سایت در فیلدی به اسم SNI (Server Name Indication) بدون رمزنگاری فرستاده می‌شود تا سروری که چند سایت دارد بداند کدام گواهی را نشان دهد. پس با HTTPS:

در مسیر دیده می‌شود در مسیر دیده نمی‌شود
IP سرور (درس شبکه) مسیر و query (/orders?card=...)
اسم سایت (SNI) و سؤال DNS هدرها و کوکی‌ها
زمان و حجم داده محتوای صفحه، فرم‌ها، رمزها

یعنی صاحب Wi-Fi می‌فهمد به کدام سایت رفته‌ای، ولی نمی‌فهمد آن‌جا چه کردی.

مثال ۶: یک CA کوچک، زنجیره‌ی اعتماد

Section titled “مثال ۶: یک CA کوچک، زنجیره‌ی اعتماد”

در دنیای واقعی، سایت گواهی را از یک CA می‌گیرد و مرورگرها به CA اعتماد دارند، نه به تک‌تک سایت‌ها. همین را در کوچک بساز. اول یک CA (یک کلید و یک گواهی خودامضا که نقش «اداره‌ی صدور» را دارد):

Terminal window
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-keyout ca.key -out ca.crt -days 365 -subj "/CN=LoopX Test CA"

حالا سایت myshop.test یک کلید می‌سازد و یک درخواست گواهی (CSR) به CA می‌دهد. در CSR کلید عمومی و اسم هست، نه کلید خصوصی:

Terminal window
openssl req -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-keyout shop.key -out shop.csr -subj "/CN=myshop.test"
printf "subjectAltName=DNS:myshop.test,DNS:www.myshop.test\n" > san.ext

CA درخواست را امضا می‌کند (در دنیای واقعی، قبلش بررسی می‌کند که درخواست‌کننده واقعاً صاحب دامنه است):

Terminal window
openssl x509 -req -in shop.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out shop.crt -days 90 -extfile san.ext
openssl x509 -in shop.crt -noout -subject -issuer -dates -ext subjectAltName
خروجی
Certificate request self-signature ok
subject=CN = myshop.test
subject=CN = myshop.test
issuer=CN = LoopX Test CA
notBefore=Oct 21 21:04:08 2026 GMT
notAfter=Jan 19 21:04:08 2027 GMT
X509v3 Subject Alternative Name:
DNS:myshop.test, DNS:www.myshop.test

حالا issuer با subject فرق دارد: گواهی سایت را CA امضا کرده. بررسی زنجیره:

Terminal window
openssl verify -CAfile ca.crt shop.crt
openssl verify -CAfile server.crt shop.crt
خروجی
shop.crt: OK
CN = myshop.test
error 20 at 0 depth lookup: unable to get local issuer certificate
error shop.crt: verification failed

با CA درست: OK. با یک «CA» بی‌ربط: رد. سرور را با گواهی جدید روشن کن. فایل https_server.py را کپی کن و فقط اسم فایل‌ها و پورت را عوض کن:

Terminal window
sed -e "s/server.crt/shop.crt/; s/server.key/shop.key/; s/8443/8445/" https_server.py > shop_server.py
python3 shop_server.py

و از ترمینال دیگر (--resolve یعنی «فرض کن این اسم به این IP است»، مثل یک خط موقت /etc/hosts در درس DNS):

Terminal window
cd ~/tls
curl -sS --resolve myshop.test:8445:127.0.0.1 https://myshop.test:8445/
curl -sS --cacert ca.crt --resolve myshop.test:8445:127.0.0.1 https://myshop.test:8445/
curl -sS --cacert ca.crt --resolve www.myshop.test:8445:127.0.0.1 https://www.myshop.test:8445/
خروجی
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html
...
<h1>صفحه‌ی امن</h1>
<h1>صفحه‌ی امن</h1>

بدون معرفی CA، curl رد کرد (این CA در فهرست سیستم نیست). با --cacert ca.crt، هر دو اسم کار کردند. نکته‌ی بزرگ: curl هرگز shop.crt را جداگانه «نمی‌شناخت»؛ فقط به CA اعتماد کرد و هر گواهی‌ای که CA امضا کرده، خودبه‌خود قبول شد. اینترنت هم دقیقاً همین‌طور کار می‌کند، فقط با چند ده CA معروف به‌جای LoopX Test CA:

Terminal window
ls /etc/ssl/certs | grep -E "ISRG|DigiCert_Global_Root_G2"
خروجی
DigiCert_Global_Root_G2.pem
ISRG_Root_X1.pem
ISRG_Root_X2.pem

این‌ها چند تا از گواهی‌های ریشهی نصب‌شده در Ubuntu هستند (ISRG همان سازمان Let’s Encrypt است). اگر کسی بتواند یک CA جعلی را به این فهرست روی کامپیوتر تو اضافه کند (مثلاً یک بدافزار، یا «نرم‌افزار امنیتی» یک شبکه‌ی اداری)، می‌تواند برای هر سایتی گواهی بسازد و ارتباط HTTPS تو را باز کند. برای همین نصب «گواهی ریشه» که کسی برایت فرستاده، تصمیم بسیار جدی است.

مرورگر فقط به چند CA ریشه اعتماد دارد؛ هر گواهی سایت باید با امضاها به یکی از آن‌ها برسد.

مثال ۷: گواهی سایت‌های واقعی و تاریخ انقضا

Section titled “مثال ۷: گواهی سایت‌های واقعی و تاریخ انقضا”

گواهی یک سایت واقعی را با openssl s_client (کلاینت TLS خام) بخوان:

Terminal window
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
نمونه
subject=C = US, ST = California, L = Los Angeles, O = Internet Corporation for Assigned Names and Numbers, CN = *.example.com
issuer=C = US, O = DigiCert Inc, CN = DigiCert Global G3 TLS ECC SHA384 2020 CA1
notBefore=Jan 15 00:00:00 2026 GMT
notAfter=Jan 15 23:59:59 2027 GMT

*.example.com یک گواهی wildcard است: برای هر زیردامنه‌ی یک‌سطحی (www.example.com، api.example.com). صادرکننده یک CA میانی DigiCert است. گواهی‌ها تاریخ انقضا دارند و این مدت مدام کوتاه‌تر شده: امروز حداکثر حدود یک سال، و گواهی‌های رایگان Let’s Encrypt فقط ۹۰ روز (برای همین تمدیدشان باید خودکار باشد). برای اینکه بدانی گواهی تا یک ماه دیگر منقضی می‌شود یا نه:

Terminal window
openssl x509 -in shop.crt -noout -checkend $((100*86400)); echo "exit=$?"
openssl x509 -in shop.crt -noout -checkend $((30*86400)); echo "exit=$?"
خروجی
Certificate will expire
exit=1
Certificate will not expire
exit=0

-checkend ثانیه می‌گوید «تا این‌قدر دیگر منقضی می‌شود؟» ($((100*86400)) حساب ۱۰۰ روز به ثانیه است). گواهی ۹۰ روزه تا ۱۰۰ روز دیگر منقضی می‌شود ولی تا ۳۰ روز دیگر نه. از همین کد خروج در یک اسکریپت cron برای هشدار قبل از انقضا استفاده می‌شود.

پشت پرده: قفل چه چیزی را تضمین نمی‌کند

Section titled “پشت پرده: قفل چه چیزی را تضمین نمی‌کند”
HTTPS تضمین می‌کند HTTPS تضمین نمی‌کند
کسی در مسیر محتوا را نمی‌خواند که سایت صادق یا قانونی است
کسی در مسیر محتوا را عوض نمی‌کند که سایت همان سایتی است که فکر می‌کنی (examp1e-bank.ir با رقم ۱ به‌جای l هم می‌تواند گواهی معتبر داشته باشد)
طرف مقابل صاحب واقعی این اسم دامنه است که سرور هک نشده یا داده‌ی تو را درست نگه می‌دارد
که کامپیوتر خودت بدافزار ندارد

بیشتر سایت‌های فیشینگ (سایت جعلی که شبیه بانک ساخته شده) امروز HTTPS و قفل دارند، چون گرفتن گواهی برای دامنه‌ای که خودشان خریده‌اند رایگان و خودکار است. قفل یعنی «ارتباط تو با این دامنه امن است»، نه «این دامنه قابل‌اعتماد است». همیشه اسم دامنه را نگاه کن.

برای سرورهای خودت، گرفتن گواهی واقعی با Let’s Encrypt رایگان و خودکار است: ابزار certbot ثابت می‌کند دامنه مال توست (مثلاً با گذاشتن یک فایل موقت روی سایت که Let’s Encrypt از اینترنت می‌خواندش)، گواهی را می‌گیرد، وب‌سرور را تنظیم می‌کند و هر دو ماه خودکار تمدید می‌کند. این کار را در دوره‌ی nginx، درس HTTPS با Let’s Encrypt، قدم‌به‌قدم انجام می‌دهی.

خطای مرورگر / curl معنی
ERR_CERT_AUTHORITY_INVALID / self-signed certificate / unable to get local issuer certificate گواهی به CA مورد اعتمادی نمی‌رسد
ERR_CERT_COMMON_NAME_INVALID / no alternative certificate subject name matches گواهی برای اسم دیگری است
ERR_CERT_DATE_INVALID / certificate has expired تاریخ گذشته (یا ساعت کامپیوتر تو غلط است!)
ERR_SSL_PROTOCOL_ERROR سرور TLS درست حرف نمی‌زند (مثلاً پورت HTTP معمولی است)
دستور کار
openssl req -x509 -newkey ec ... -keyout k -out c گواهی خودامضا
openssl x509 -in c -noout -subject -issuer -dates خواندن گواهی
openssl verify -CAfile ca.crt c بررسی زنجیره
openssl x509 -in c -noout -checkend N انقضا تا N ثانیه‌ی دیگر؟
openssl s_client -connect host:443 -servername host دیدن گواهی یک سایت
curl -v https://... دیدن دست‌دادن و بررسی
curl --cacert ca.crt اعتماد به یک CA مشخص
curl -k بی‌خیال بررسی (فقط آزمایش!)

۱) -k و «Proceed anyway» به‌عنوان راه‌حل

Section titled “۱) -k و «Proceed anyway» به‌عنوان راه‌حل”

خطای گواهی را با curl -k یا کلیک روی «ادامه» دور زدن، دقیقاً همان کاری است که مهاجم می‌خواهد. راه‌حل: علت را پیدا کن (اسم، تاریخ، CA)؛ برای سرورهای داخلی، CA داخلی و --cacert.

۲) گواهی برای یک اسم، سایت روی چند اسم

Section titled “۲) گواهی برای یک اسم، سایت روی چند اسم”

راه‌حل: همه‌ی اسم‌ها (example.com، www.example.com) در SAN، یا wildcard.

سایت یک صبح با خطای ERR_CERT_DATE_INVALID بالا می‌آید. راه‌حل: تمدید خودکار (certbot) و یک بررسی جدا با -checkend که هشدار بدهد.

server.key با مجوز 644، در گیت، یا فرستاده با ایمیل. راه‌حل: مجوز 600، فقط روی سرور، و اگر لو رفت، باطل کردن گواهی و ساختن کلید جدید.

۵) «قفل دارد پس امن است»

Section titled “۵) «قفل دارد پس امن است»”

راه‌حل: قفل فقط ارتباط را امن می‌کند؛ اسم دامنه را با دقت بخوان و رمز را فقط در سایتی بزن که خودت آدرسش را تایپ کرده‌ای یا از قبل نشانه‌گذاری کرده‌ای.

تمرینآسان

گواهی shop.crt مثال ۶ را بخوان و بگو: برای کدام اسم‌هاست، چه کسی صادرش کرده و کی منقضی می‌شود. بعد بگو آیا برای api.myshop.test هم معتبر است.

دیدن جواب
Terminal window
openssl x509 -in shop.crt -noout -subject -issuer -enddate -ext subjectAltName
خروجی
subject=CN = myshop.test
issuer=CN = LoopX Test CA
notAfter=Jan 19 21:04:08 2027 GMT
X509v3 Subject Alternative Name:
DNS:myshop.test, DNS:www.myshop.test

اسم‌ها: myshop.test و www.myshop.test. صادرکننده: LoopX Test CA. انقضا: ۱۹ ژانویه‌ی ۲۰۲۷. برای api.myshop.test معتبر نیست: در SAN نیست و wildcard هم نیست. curl برایش no alternative certificate subject name matches می‌داد.

تمرینمتوسط

یک گواهی جدید با همان CA بساز که برای هر زیردامنه‌ی myshop.test معتبر باشد (wildcard) و با --resolve ثابت کن api.myshop.test با آن کار می‌کند. آیا myshop.test (بدون زیردامنه) هم کار می‌کند؟

دیدن جواب
Terminal window
openssl req -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-keyout wild.key -out wild.csr -subj "/CN=*.myshop.test"
printf "subjectAltName=DNS:*.myshop.test\n" > wild.ext
openssl x509 -req -in wild.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out wild.crt -days 90 -extfile wild.ext
sed -e "s/server.crt/wild.crt/; s/server.key/wild.key/; s/8443/8446/" https_server.py > wild_server.py
python3 wild_server.py

در ترمینال دیگر:

Terminal window
curl -sS --cacert ca.crt --resolve api.myshop.test:8446:127.0.0.1 https://api.myshop.test:8446/
curl -sS --cacert ca.crt --resolve myshop.test:8446:127.0.0.1 https://myshop.test:8446/
خروجی
<h1>صفحه‌ی امن</h1>
curl: (60) SSL: no alternative certificate subject name matches target host name 'myshop.test'
...

*.myshop.test همه‌ی زیردامنه‌های یک‌سطحی را می‌پوشاند (api، www، shop)، ولی نه خود myshop.test و نه دوسطحی‌ها (a.b.myshop.test). برای همین گواهی‌های واقعی معمولاً هر دو را دارند: DNS:myshop.test,DNS:*.myshop.test.

تمرینسخت

یک اسکریپت کوچک cert-check.sh بنویس که برای هر فایل گواهی که به آن داده می‌شود، اسم و تاریخ انقضا را چاپ کند و اگر کمتر از ۳۰ روز مانده بود، «هشدار» بنویسد و در آخر با کد ۱ خارج شود. روی server.crt (۳۰ روزه) و shop.crt (۹۰ روزه) امتحانش کن.

دیدن جواب
cert-check.sh
#!/usr/bin/env bash
status=0
for cert in "$@"; do
name=$(openssl x509 -in "$cert" -noout -subject | sed 's/.*CN = //')
end=$(openssl x509 -in "$cert" -noout -enddate | cut -d= -f2)
if openssl x509 -in "$cert" -noout -checkend $((30*86400)) > /dev/null; then
echo "OK $name (until $end)"
else
echo "WARNING $name expires soon: $end"
status=1
fi
done
exit $status
Terminal window
chmod +x cert-check.sh
./cert-check.sh server.crt shop.crt; echo "exit=$?"
خروجی
WARNING localhost expires soon: Nov 20 21:03:40 2026 GMT
OK myshop.test (until Jan 19 21:04:08 2027 GMT)
exit=1

server.crt دقیقاً ۳۰ روز اعتبار داشت و چند ثانیه از ساختش گذشته، پس «کمتر از ۳۰ روز» مانده. "$@" یعنی همه‌ی آرگومان‌های اسکریپت، sed و cut تکه‌ی لازم را از خروجی جدا می‌کنند (همه را در دوره‌های لینوکس و bash مفصل می‌بینی). با cron (هر صبح) و یک ایمیل یا پیام، همین اسکریپت جلوی صبح‌های «سایت با خطای گواهی بالا آمده» را می‌گیرد.

آزمونک
  1. HTTPS کدام سه چیز را تضمین می‌کند؟

  2. گواهی دیجیتال چه چیزی را به هم گره می‌زند؟

  3. خطای self-signed certificate یعنی…

  4. با HTTPS، صاحب Wi-Fi کافه چه چیزی را می‌تواند ببیند؟

  5. یک سایت فیشینگ قفل سبز دارد. یعنی…

  6. چرا curl -k در اسکریپت‌های واقعی خطرناک است؟

  • HTTPS = HTTP داخل TLS: محرمانگی، درستی و هویت سرور.
  • TLS با کلید عمومی/خصوصی هویت را ثابت می‌کند و یک کلید متقارن موقت برای بقیه‌ی گفت‌وگو می‌سازد.
  • گواهی کلید عمومی را به اسم دامنه گره می‌زند و یک CA امضایش می‌کند؛ مرورگر زنجیره را تا یک ریشه‌ی نصب‌شده بررسی می‌کند، به‌علاوه‌ی اسم و تاریخ.
  • خطاهای اصلی: CA ناشناس (خودامضا)، اسم نادرست، تاریخ گذشته. راه‌حل هیچ‌کدام -k نیست.
  • با HTTPS هم اسم سایت (SNI) و IP دیده می‌شوند؛ محتوا، مسیر و کوکی نه.
  • قفل یعنی ارتباط امن با این دامنه، نه اینکه دامنه قابل‌اعتماد است. گواهی واقعی: Let’s Encrypt (دوره‌ی nginx).
برگه‌ی تقلب این درس
دستورکاری که می‌کند
curl -v https://siteدیدن دست‌دادن TLS و گواهی
openssl x509 -in c.crt -noout -subject -issuer -datesخواندن گواهی
openssl s_client -connect host:443 -servername hostگواهی یک سایت واقعی
openssl verify -CAfile ca.crt c.crtبررسی زنجیره
openssl x509 -in c.crt -noout -checkend 2592000انقضا تا ۳۰ روز دیگر؟
curl --cacert ca.crtاعتماد به CA مشخص
curl -kخاموش کردن بررسی (فقط آزمایش)
/etc/ssl/certsگواهی‌های ریشه‌ی سیستم