nginx میں mTLS سے client certificates لازمی کریں
nginx میں admin panel کو mTLS سے محفوظ کریں: openssl سے private CA بنائیں، client certificates جاری کریں، اور certificate کے بغیر request مسترد کریں۔
mTLS کیا کرتا ہے
Mutual TLS، جسے عموماً mTLS لکھا جاتا ہے، nginx کو پابند کرتا ہے کہ وہ ہر client سے certificate طلب کرے۔ اگر certificate موجود نہ ہو یا اسے ایسے certificate authority (CA) نے جاری نہ کیا ہو جسے آپ control کرتے ہیں، تو nginx request مسترد کر دیتا ہے۔ یہ جانچ TLS (transport layer security) handshake کے اندر ہوتی ہے۔ اس لیے valid client certificate کے بغیر آنے والی request آپ کی application تک بالکل نہیں پہنچتی۔ یہی اس طریقے کا فائدہ ہے۔ آپ admin panel یا metrics endpoint کو public internet پر login page کے بغیر رکھ سکتے ہیں، اور bot کے لیے اندازہ لگانے کو کچھ نہیں رہتا۔
اس کے لیے configuration مختصر ہے۔ openssl سے بنائی گئی ایک private CA، ہر شخص کے لیے ایک certificate، اور nginx server block میں تین directives۔ یہ configuration ایک سال تک قابلِ اعتماد طور پر چلتی رہے گی یا نہیں، اس کا فیصلہ operational کام کرتا ہے۔ اسی لیے اس guide کا زیادہ حصہ certificate lifetimes، revocation، ہر شخص کے الگ certificates، اور اس صورتِ حال سے نمٹنے پر ہے جب client مسترد ہو جائے اور کسی کو معلوم نہ ہو کہ وجہ کیا ہے۔
دو زنجیریں، ایک نہیں
mTLS setup میں دو certificate chains ہوتی ہیں اور ان کا ایک دوسرے سے کوئی تعلق نہیں ہوتا۔ انہیں یکجا کرنا وہ پہلی غلطی ہے جو تقریباً ہر شخص کرتا ہے۔
پہلی chain server کی ہوتی ہے۔ آپ کا VPS admin.example.com کے لیے ایک certificate پیش کرتا ہے، جو Let's Encrypt جیسی public CA جاری کرتی ہے، اور browser اسے operating system کے ساتھ فراہم کیے گئے root store کے مطابق verify کرتا ہے۔ mTLS اس عمل کے اس حصے کو تبدیل نہیں کرتا۔ اگر certbot آج آپ کے لیے یہ certificate جاری کرتا ہے تو اسے بالکل اسی طرح رہنے دیں: certbot کے ساتھ nginx کے لیے Let's Encrypt certificate جاری کرنا دیکھیں۔
دوسری chain client کی ہوتی ہے۔ آپ اپنی ایک چھوٹی CA بناتے ہیں، ہر مطلوبہ شخص کے لیے ایک certificate sign کرتے ہیں، اور nginx کو بتاتے ہیں کہ clients کی جانچ کرتے وقت صرف اسی CA پر اعتماد کرے۔ کسی public root store کو آپ کی CA کا علم نہیں ہوتا اور نہ ہی اسے اس کی ضرورت ہے۔ صرف nginx کو اس پر اعتماد کرنا ہوتا ہے، اور یہ اعتماد ssl_client_certificate file کے ذریعے قائم ہوتا ہے۔
اس لیے ssl_client_certificate nginx کے پیش کیے گئے certificate کو کبھی متاثر نہیں کرتا، اور Let's Encrypt chain اس بات کو کبھی متاثر نہیں کرتی کہ کن clients کو رسائی ملے گی۔ ssl_client_certificate کو fullchain.pem کی طرف point کرنے سے وہ نتیجہ نہیں نکلتا جو بظاہر نکلتا ہے: یہ directive ان issuers کے نام متعین کرتی ہے جہاں سے ایک client certificate جاری ہو سکتا ہے، یعنی connection کے دوسرے سرے سے۔ اپنے outbound کام کے لیے server کو اپنی CA پر اعتماد کرانا ایک الگ کام ہے، جس کی وضاحت Ubuntu trust store میں اپنی CA شامل کرنا میں کی گئی ہے، اور system trust store وہ store نہیں ہے جسے nginx client کو verify کرتے وقت پڑھتا ہے۔
openssl کے ذریعے اپنا client CA بنائیں
CA کو web server کے علاوہ کسی جگہ بنائیں۔ nginx کو صرف CA کے public certificate کی ضرورت ہوتی ہے۔ CA کی private key نئے client certificates پر دستخط کرتی ہے، اس لیے اسے internet-facing machine پر رکھنے کا مطلب ہے کہ ایک breach کی صورت میں attacker اپنی مرضی سے اپنے لیے valid clients بنا سکتا ہے۔
mkdir -p ~/client-ca/certs ~/client-ca/newcerts ~/client-ca/private ~/client-ca/csr
cd ~/client-ca
chmod 700 private
touch index.txt
echo 1000 > serial
echo 1000 > crlnumberindex.txt، serial اور crlnumber CA database ہیں۔ openssl ca ان کے بغیر run نہیں ہوتا۔ بعد میں revocation بھی انہی کی وجہ سے ممکن ہوتی ہے، کیونکہ revocation list serial numbers درج کرتی ہے۔ اس لیے CA کو یاد رکھنا ہوتا ہے کہ کون سا serial کس کو دیا گیا تھا۔
~/client-ca/openssl.cnf لکھیں۔ dir کو اس directory کے حقیقی path پر set کریں، کیونکہ openssl ca، ~ کو expand نہیں کرتا۔
[ ca ]
default_ca = client_ca
[ client_ca ]
dir = /home/you/client-ca
database = $dir/index.txt
new_certs_dir = $dir/newcerts
certificate = $dir/ca.crt
private_key = $dir/private/ca.key
serial = $dir/serial
crlnumber = $dir/crlnumber
default_md = sha256
default_days = 365
default_crl_days = 30
policy = policy_loose
rand_serial = no
unique_subject = no
email_in_dn = no
[ policy_loose ]
commonName = supplied
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
emailAddress = optional
[ client_ext ]
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuerاب CA key اور اس کا self-signed certificate بنائیں:
openssl genrsa -aes256 -out private/ca.key 4096
chmod 600 private/ca.key
openssl req -x509 -new -key private/ca.key -sha256 -days 3650 \
-subj "/O=Example Ops/CN=Example Ops Client CA" \
-addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-out ca.crt-aes256 CA key پر passphrase لگاتا ہے، اس لیے ہر signing run میں یہ passphrase طلب کرتا ہے۔ یہی اس کا مقصد ہے۔ بنائی گئی چیز کی تصدیق کریں:
openssl x509 -in ca.crt -noout -subject -dates -ext basicConstraintssubject میں آپ کا CA ہونا چاہیے اور validity دس سال کی ہونی چاہیے۔ extension line CA:TRUE, pathlen:0 ہونی چاہیے۔ pathlen:0 کا مطلب ہے کہ یہ CA end certificates پر دستخط کر سکتا ہے، لیکن کسی دوسرے CA پر دستخط نہیں کر سکتا۔ اس سے chain بالکل ایک level گہری رہتی ہے اور آپ ssl_verify_depth کو جوں کا توں چھوڑ سکتے ہیں۔
ہر فرد کے لیے ایک client certificate جاری کریں
ہر فرد کے لیے ایک certificate رکھیں۔ پوری ٹیم کے لیے کبھی ایک مشترکہ certificate استعمال نہ کریں، کیونکہ مشترکہ certificate کو revoke کرنے سے سب کی رسائی بند ہو جاتی ہے، اور یہ معلوم نہیں ہوتا کہ درخواست کس نے بھیجی۔
openssl genrsa -out private/alice.key 2048
openssl req -new -key private/alice.key -out csr/alice.csr \
-subj "/O=Example Ops/CN=alice"
openssl ca -config openssl.cnf -extensions client_ext \
-days 365 -notext -in csr/alice.csr -out certs/alice.crtopenssl ca اس certificate کو دکھاتا ہے جس پر دستخط ہونے والے ہیں، CA passphrase مانگتا ہے، تصدیق کے لیے دو بار پوچھتا ہے، پھر ایک سطر index.txt میں شامل کر دیتا ہے۔ اسے script میں شامل کرتے وقت -batch لگائیں۔ client_ext section اہم ہے، کیونکہ اس میں ایک سطر extendedKeyUsage = clientAuth موجود ہے۔ جس certificate میں extended key usage کے تحت صرف serverAuth درج ہو، اسے client authentication کے لیے ناموزوں سمجھ کر مسترد کر دیا جاتا ہے۔ اس لیے مقصد واضح طور پر درج کریں، محض امید نہ رکھیں۔
کسی کو کچھ دینے سے پہلے CA کے مقابلے میں key اور certificate کے جوڑے کی تصدیق کریں:
openssl verify -CAfile ca.crt certs/alice.crtیہ certs/alice.crt: OK دکھاتا ہے۔ کسی بھی دوسرے output کا مطلب ہے کہ certificate اور CA ایک دوسرے سے مطابقت نہیں رکھتے۔ کوئی بھی nginx configuration اسے درست نہیں کر سکتی۔
key اور certificate کو ایک ہی file میں bundle کریں تاکہ browser اسے import کر سکے:
openssl pkcs12 -export -inkey private/alice.key -in certs/alice.crt \
-name "alice at example ops" -out alice.p12export کے دوران password مانگا جاتا ہے، جو منتقلی کے دوران file کو محفوظ رکھتا ہے۔ file اور password مختلف channels کے ذریعے بھیجیں، اور لوگوں کو الگ .p12 کے بجائے .key دیں۔ CA کو bundle میں شامل کرنے کے لیے -certfile ca.crt بھی شامل کیا جا سکتا ہے، لیکن nginx کو اس کی ضرورت نہیں ہے۔ nginx کے پاس پہلے ہی ca.crt موجود ہے، اس لیے اسی CA کے دستخط شدہ certificate کی خود تصدیق ہو جاتی ہے۔
Ubuntu 24.04 کے ساتھ آنے والا OpenSSL 3 موجودہ encryption استعمال کرتے ہوئے PKCS#12 files بناتا ہے، اور August 2026 تک استعمال ہونے والے browsers اور operating systems انہیں پڑھ سکتے ہیں۔ اگر کوئی پرانا importer اس file کو قبول نہ کرے تو -legacy شامل کرکے دوبارہ export کریں۔ اس سے وہ پرانے algorithms استعمال ہوں گے جن کی اس importer کو توقع ہے۔ اس flag کو استعمال کرنے سے پہلے importer کا دیا ہوا message پڑھیں۔
ssl_client_certificate اور ssl_verify_client کے ساتھ nginx configure کریں
CA certificate، اور صرف CA certificate، سرور پر copy کریں۔
scp ca.crt user@admin.example.com:/tmp/client-ca.crt
ssh user@admin.example.com \
'sudo install -o root -g root -m 644 /tmp/client-ca.crt /etc/nginx/client-ca.crt'یہاں Mode 644 درست ہے۔ CA certificate عوامی معلومات ہوتی ہے۔ CA key آپ کے workstation پر رہتی ہے۔
اب اس server block میں تین directives شامل کریں جو پہلے ہی TLS terminate کرتا ہے:
server {
listen 443 ssl;
server_name admin.example.com;
ssl_certificate /etc/letsencrypt/live/admin.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.example.com/privkey.pem;
ssl_client_certificate /etc/nginx/client-ca.crt;
ssl_verify_client on;
ssl_verify_depth 1;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}ssl_verify_depth 1 nginx کی default قدر ہے۔ اس کا مطلب ہے کہ client certificate پر اسی file میں موجود CA کے ذریعے براہ راست دستخط ہونے چاہییں۔ اگر آپ intermediate شامل کریں تو اسے صرف اسی صورت میں بڑھائیں۔ nginx handshake کے دوران ssl_client_certificate سے subject names بھی client کو بھیجتا ہے۔ اسی سے browser کو معلوم ہوتا ہے کہ اسے اپنے کون سے certificates پیش کرنے چاہییں۔ اسی وجہ سے ssl_client_certificate استعمال کریں، ssl_trusted_certificate نہیں۔ دونوں ایک ہی طریقے سے verification کرتے ہیں، لیکن ssl_trusted_certificate کوئی فہرست نہیں بھیجتا۔
Ubuntu 24.04 کے ساتھ nginx 1.24 آتا ہے، جس میں HTTP/2 کو listen line پر listen 443 ssl http2; کے طور پر شامل کیا جاتا ہے۔ nginx 1.25.1 اور اس کے بعد کے versions میں یہ طریقہ deprecated ہے، اور HTTP/2 کے لیے الگ directive http2 on; استعمال کی جاتی ہے۔ دونوں میں سے کسی بھی انتخاب سے certificate check تبدیل نہیں ہوتا۔
Reload کریں اور نتیجہ پڑھیں:
sudo nginx -t && sudo systemctl reload nginx
curl -i https://admin.example.com/nginx -t، syntax is ok اور test is successful print کرتا ہے۔ curl call میں کوئی certificate شامل نہیں، اس لیے اسے body No required SSL certificate was sent کے ساتھ 400 Bad Request واپس آنا چاہیے۔ اس کا مطلب ہے کہ nginx نے اپنی سطح پر درخواست مسترد کر دی۔ اس سے تصدیق ہوتی ہے کہ configuration فعال ہے اور application کو درخواست بھیجی ہی نہیں گئی۔ اب درست طریقے سے آزمائیں:
curl --cert certs/alice.crt --key private/alice.key https://admin.example.com/اس سے وہی response آنا چاہیے جو آپ کی application serve کرتی ہے۔
server block میں gate کیوں شامل ہونا چاہیے
Certificate کا تبادلہ TLS handshake کے دوران ہوتا ہے، اس سے پہلے کہ nginx request line پڑھے۔ اس لیے اس مرحلے پر nginx کو معلوم نہیں ہوتا کہ درخواست کس location میں جائے گی۔ ssl_verify_client on; کو location کے اندر رکھنے سے client سے connection کے درمیان دوبارہ negotiation کی درخواست کی جاتی ہے۔ TLS 1.3 نے renegotiation ختم کر دی ہے اور HTTP/2 اسے منع کرتا ہے۔ اس لیے موجودہ stack میں یہ طریقہ prompt دکھانے کے بجائے ناکام ہو جاتا ہے۔
Scope خود متعین کریں۔ server level پر certificate طلب کریں، پھر ہر location کے لیے فیصلہ کریں:
ssl_verify_client optional;
location /metrics {
if ($ssl_client_verify != SUCCESS) { return 403; }
proxy_pass http://127.0.0.1:9090;
}
location /healthz {
proxy_pass http://127.0.0.1:8080;
}$ssl_client_verify میں SUCCESS ہوتا ہے، یا NONE جب client نے کچھ نہیں بھیجا، یا FAILED: کے بعد وجہ درج ہوتی ہے۔ optional کے ساتھ nginx certificate طلب کرتا ہے اور صرف اس صورت میں اس کی تصدیق کرتا ہے جب certificate موصول ہو۔ اسی سے اوپر موجود public /healthz path کام کرتا ہے جبکہ /metrics بند رہتا ہے۔ جو certificate بھیجا جائے لیکن verification میں ناکام ہو، nginx اسے اسی مرحلے پر مسترد کر دیتا ہے۔ اگر آپ ناکام certificate کا خود معائنہ کرنا چاہتے ہیں تو یہ optional_no_ca ہے۔ اس صورت میں آپ کے اپنے test کو SUCCESS کے علاوہ ہر value کو refusal سمجھنا ہوگا۔
nginx اس مقصد کے لیے non-standard status codes استعمال کرتا ہے، اور error_page انہیں پکڑ سکتا ہے تاکہ مسترد کیے گئے visitor کو صرف 400 کے بجائے وضاحت ملے:
error_page 495 496 = @needcert;
location @needcert {
default_type text/plain;
return 200 "This host requires a client certificate. Ask ops for one.\n";
}495 کا مطلب ہے کہ client certificate کی verification ناکام ہو گئی۔ 496 کا مطلب ہے کہ client نے کوئی certificate پیش نہیں کیا۔ اس page کو plain text رکھیں، کیونکہ اسے پڑھنے والے شخص کے پاس نہ session ہے اور نہ account۔
براؤزر میں client certificate کیسے انسٹال کروں؟
Firefox اپنا certificate store استعمال کرتا ہے: Settings، پھر Privacy and Security، پھر View Certificates، پھر Your Certificates tab، پھر Import منتخب کریں۔ اس کے بعد .p12 فائل منتخب کریں اور اس کا password درج کریں۔
Windows اور macOS پر Chrome اور Edge operating system store استعمال کرتے ہیں، اس لیے .p12 فائل کھولنے سے system import wizard شروع ہو جاتا ہے۔ Linux پر Chrome آپ کی home directory میں موجود الگ NSS (network security services) database سے certificates پڑھتا ہے۔ اس لیے command line tool زیادہ قابلِ اعتماد طریقہ ہے:
sudo apt install -y libnss3-tools
pk12util -d sql:$HOME/.pki/nssdb -i alice.p12اس کے بعد site لوڈ کریں۔ براؤزر پوچھے گا کہ کون سا certificate بھیجنا ہے۔ Chrome اس انتخاب کو موجودہ browser session کے اختتام تک یاد رکھتا ہے، اس لیے دوبارہ پوچھنے کے لیے browser restart کریں۔ Certificate ایک machine کے ایک browser profile میں محفوظ ہوتا ہے۔ اس لیے Firefox میں import کیا گیا certificate Chrome میں نظر نہیں آتا، اور دونوں certificates آپ کے phone میں بھی دستیاب نہیں ہوتے۔
curl --cert کے ذریعے جانچ
curl کے ذریعے debugging کریں، کیونکہ یہ بتاتا ہے کہ اس نے کیا کارروائی کی۔
curl -v --cert certs/alice.crt --key private/alice.key https://admin.example.com/آپ certificate اور key کو ایک ہی PEM file میں جوڑ کر اسے --cert alice.pem کے طور پر دے سکتے ہیں۔ اگر key پر passphrase ہو تو curl اسے طلب کرتا ہے۔ یہ --cert alice.pem:passphrase بھی قبول کرتا ہے، لیکن اس صورت میں passphrase آپ کی shell history میں محفوظ ہو جاتا ہے، اس لیے prompt استعمال کریں۔
nginx کو موردِ الزام ٹھہرانے سے پہلے دو جانچیں ضرور کریں۔ پہلی جانچ یہ ہے کہ certificate اور key ایک ہی جوڑا ہوں:
openssl x509 -noout -pubkey -in certs/alice.crt | openssl sha256
openssl pkey -pubout -in private/alice.key | openssl sha256دو یکساں hashes کا مطلب ہے کہ files ایک دوسرے سے متعلق ہیں۔ دو مختلف hashes کا مطلب ہے کہ آپ نے دو افراد کی files آپس میں ملا دی ہیں، اور کوئی client اس وجہ کی نشاندہی خود نہیں کرے گا۔
دوسری جانچ یہ ہے کہ server کو آپ کے CA کا مطالبہ کرنا چاہیے:
openssl s_client -connect admin.example.com:443 -servername admin.example.com </dev/nulloutput میں Acceptable client certificate CA names block تلاش کریں، اور اس کے اندر اپنے CA کا subject دیکھیں۔ اگر یہ block مکمل طور پر غائب ہو تو nginx اس server block پر certificate طلب نہیں کر رہا جس نے جواب دیا ہے۔ اس کا مطلب ہے کہ آپ کی directives کسی دوسرے server block میں چلی گئی ہیں، جو اکثر default server ہوتا ہے۔
application کو client CN منتقل کرنا
Certificate میں موجود ہوتا ہے کہ درخواست کس نے بھیجی، لیکن proxy کے پیچھے موجود application TLS layer نہیں دیکھ سکتی۔ اس لیے nginx کو یہ نام آگے منتقل کرنا ہوگا۔
map $ssl_client_s_dn $client_cn {
default "";
"~,?CN=(?<cn>[^,]+)" $cn;
}$ssl_client_s_dn میں subject distinguished name، RFC 2253 format میں موجود ہوتا ہے۔ یہ CN=alice,O=Example Ops جیسا دکھائی دیتا ہے۔ map، CN field کو $client_cn میں منتقل کرتا ہے۔ CN کو سادہ username رکھیں، کیونکہ اس format میں CN کے اندر comma کو escape کیا جاتا ہے اور اوپر دیا گیا مختصر regular expression اس escape کو handle نہیں کرتا۔
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Client-Cert-CN $client_cn;
proxy_set_header X-Client-Cert-Serial $ssl_client_serial;
}proxy_set_header caller کی جانب سے بھیجے گئے اسی نام کے ہر header کو replace کرتا ہے، اس لیے کوئی بھی شخص اس location کے ذریعے X-Client-Cert-CN میں جعلی قدر شامل نہیں کر سکتا۔ اس بات کو برقرار رکھنے کے لیے دو شرائط ضروری ہیں۔ nginx، proxy_set_header کو outer level سے صرف اس وقت inherit کرتا ہے جب inner level میں اپنی کوئی proxy_set_header لائن موجود نہ ہو۔ اس لیے دوسری location میں ایک proxy_set_header لائن شامل کرنے سے اوپر set کیے گئے تمام headers خاموشی سے ختم ہو جاتے ہیں، جن میں یہ header بھی شامل ہے۔ دوسری شرط یہ ہے کہ application تک nginx کے علاوہ کسی اور راستے سے رسائی نہ ہو۔ اس کے لیے اسے 127.0.0.1 پر bind کریں، نہ کہ 0.0.0.0 پر، کیونکہ public port پر چلنے والی application internet سے براہ راست آنے والا forged header پڑھ لے گی۔ Proxy کی جانب کی configuration nginx reverse proxy configuration کی سطر بہ سطر وضاحت میں دی گئی ہے۔ اگر application کو صرف نام کے بجائے مکمل certificate درکار ہو تو $ssl_client_escaped_cert اسے URL-encoded شکل میں header کے اندر محفوظ طریقے سے منتقل کرتا ہے۔
ایک client certificate کو کیسے revoke کریں؟
کوئی شخص ادارہ چھوڑ دیتا ہے، یا اس کا laptop گم ہو جاتا ہے۔ آپ صرف اسی certificate کو revoke کرتے ہیں اور باقی سب کے کام میں رکاوٹ نہیں آتی۔ ہر شخص کے لیے الگ certificate جاری کرنے کی یہی بنیادی وجہ ہے۔
cd ~/client-ca
openssl ca -config openssl.cnf -revoke certs/alice.crt
openssl ca -config openssl.cnf -gencrl -out crl.pemپہلی command index.txt میں اس serial کی line کو V سے R میں تبدیل کرتی ہے۔ دوسری command certificate revocation list (CRL) لکھتی ہے۔ یہ ایک signed file ہوتی ہے جس میں revoked serial numbers درج ہوتے ہیں۔ اسے deploy کریں اور ssl_crl /etc/nginx/client-ca.crl; کے ذریعے nginx کو اس file کی طرف point کریں، جیسے دوسری directives کے ساتھ کیا جاتا ہے۔
scp crl.pem user@admin.example.com:/tmp/client-ca.crl
ssh user@admin.example.com 'sudo install -m 644 /tmp/client-ca.crl /etc/nginx/client-ca.crl && sudo nginx -t && sudo systemctl reload nginx'یہ وہ مسئلہ ہے جو سب کے لیے دروازہ بند کر دیتا ہے۔ CRL میں nextUpdate date ہوتی ہے، جسے default_crl_days مقرر کرتا ہے۔ اوپر کی configuration میں یہ قدر 30 ہے۔ یہ date گزرنے کے بعد OpenSSL اس list کو stale سمجھتا ہے اور CRL has expired کے ساتھ ہر client certificate کی verification ناکام کر دیتا ہے، صرف revoked certificate کی نہیں۔ nginx اپنی configuration load کرتے وقت file پڑھتا ہے، اس لیے disk پر نئی CRL رکھنے سے reload تک کوئی اثر نہیں ہوتا۔ CRL کو ایسے schedule پر دوبارہ generate اور reload کریں جو اس مدت سے کافی کم ہو، مثلاً 30 دن کی مدت کے لیے ہر ہفتے۔ Copy کرنے سے پہلے dates چیک کریں:
openssl crl -in crl.pem -noout -lastupdate -nextupdateچند users کے لیے ایک چھوٹا طریقہ بھی موجود ہے۔ CA آپ کی اپنی ہے، اس لیے nginx براہ راست کسی serial کو refuse کر سکتا ہے اور CRL کا پورا طریقہ چھوڑا جا سکتا ہے:
map $ssl_client_serial $revoked {
default 0;
"1002" 1;
}اس کے ساتھ location میں if ($revoked) { return 403; } شامل کریں۔ اس کی کوئی expiry date نہیں ہوتی جسے بھولنے کا خطرہ ہو۔ لیکن یہ configuration منتقل نہیں ہوتی، اس لیے آپ کے CA پر اعتماد کرنے والی دوسری کوئی چیز اس کے بارے میں کچھ نہیں جانتی۔ ایک application کے سامنے صرف ایک nginx ہو تو یہ سادہ اور مناسب حل ہے۔ جب ایک سے زیادہ gateways ہوں تو CRL استعمال کریں۔
کلائنٹ certificates کتنی مدت کے لیے مؤثر ہونے چاہییں؟
کلائنٹ certificates کو ایک سال کے لیے مؤثر رکھیں، یا اگر آپ دوبارہ جاری کرنے کا کام برداشت کر سکتے ہیں تو اس سے کم مدت رکھیں۔ یہاں خاموش failure expiry کی وجہ سے ہوتا ہے، کیونکہ holder کو پہلے سے کوئی اطلاع نہیں ملتی۔ وہ ایک صبح panel کھولتا ہے، nginx connection مسترد کر دیتا ہے، اور browser اس انکار کو اپنے الفاظ میں بیان کرتا ہے۔ ان الفاظ میں عموماً expired شامل نہیں ہوتا۔ CA کو دس سال کے لیے مؤثر رکھیں اور اس کی expiry date ایسی جگہ درج کریں جہاں آپ واقعی اسے دیکھیں، کیونکہ CA certificate کے expire ہوتے ہی اس کے تحت جاری تمام certificates اسی دن verification میں ناکام ہو جاتے ہیں۔
یہ دو commands آپ کو اس مسئلے سے پہلے باخبر رکھتی ہیں:
openssl x509 -in certs/alice.crt -noout -subject -serial -enddate
awk -F'\t' '{print $1, $2, $4}' ~/client-ca/index.txtindex.txt کے پہلے column میں status ہوتا ہے: valid کے لیے V، revoked کے لیے R، اور expired کے لیے E۔ دوسرے column میں YYMMDDHHMMSSZ format میں expiry ہوتی ہے، جبکہ چوتھا column serial ہوتا ہے۔ یہ file اس بات کا واحد record ہے کہ کس کے پاس کون سا certificate ہے، اس لیے اس کا backup CA key کے ساتھ رکھیں اور دونوں کو secrets سمجھیں۔
Renewal نیا certificate جاری کرنا ہے، extension نہیں۔ نئی key اور CSR (certificate signing request) بنائیں، اسے sign کریں، متعلقہ شخص کو فراہم کریں، پھر جب وہ تصدیق کر دے کہ نیا certificate کام کر رہا ہے تو پرانے certificate کو revoke کریں۔
mTLS کن چیزوں سے تحفظ فراہم کرتا ہے، اور کن چیزوں سے نہیں
یہ غیر مصدقہ رسائی ختم کرتا ہے۔ جو scanner آپ کا hostname تلاش کرتا ہے، اسے handshake کے دوران مسترد کر دیا جاتا ہے۔ اس لیے وہ کبھی HTTP request نہیں بھیجتا، login form نہیں دیکھتا، اور چوری شدہ password آزمانے تک نہیں پہنچتا۔ Credential stuffing کے لیے کوئی جگہ نہیں رہتی۔ application کے login flow میں موجود vulnerability تک certificate کے بغیر کوئی نہیں پہنچ سکتا۔ یہ اس shared secret کو بھی ختم کرتا ہے جسے لوگ chat میں paste کرتے ہیں، کیونکہ private key ایک file ہوتی ہے جسے حادثاتی طور پر copy کرنا مشکل ہوتا ہے۔
یہ compromised client کے بارے میں کچھ نہیں کرتا۔ laptop پر موجود malware کے پاس key file ہوتی ہے، اور owner کے اسے type کرتے ہی passphrase بھی اس کے پاس آ جاتا ہے۔ server کے لیے وہ attacker بالکل legitimate user جیسا دکھائی دیتا ہے، کیونکہ certificate کسی شخص کی موجودگی نہیں بلکہ ایک file رکھنے کا ثبوت ہوتا ہے۔ .p12 password اور full disk encryption اب بھی اہم رہتے ہیں۔
یہ authorization بھی نہیں ہے۔ ہر valid certificate کو اس server block کی فراہم کردہ تمام چیزوں تک رسائی ملتی ہے، جب تک آپ $client_cn کو check کرکے اس کی value کے مطابق کارروائی نہ کریں۔ Default طور پر دو certificate holders کو یکساں access حاصل ہوتا ہے۔
یہ صرف nginx کے ذریعے گزرنے والے path کی حفاظت کرتا ہے۔ اگر application public port پر بھی listen کرتی ہے تو اس کے سامنے mTLS محض ظاہری تحفظ ہے۔ app کو 127.0.0.1 پر bind کریں اور اس کے port پر firewall بند رکھیں۔ اسی machine تک پہنچنے کا دوسرا راستہ SSH ہے، اور اسے بھی اتنی ہی توجہ درکار ہے۔ اس کی تفصیل اپنے VPS پر SSH access کو harden کرنا میں دی گئی ہے۔
ایک آخری حد بھی ہے، جو اسے فعال کرتے ہی سامنے آتی ہے۔ جو بھی چیز certificate پیش نہیں کر سکتی، وہ کام کرنا بند کر دیتی ہے: uptime monitor، payment provider کا webhook، RSS reader، یا ایسی mobile app جس تک کوئی certificate store دستیاب نہ ہو۔ ssl_verify_client on set کرنے سے پہلے ان کے بارے میں فیصلہ کریں، کیونکہ failure مکمل ہوتا ہے اور دوسری جانب عموماً خاموش رہتا ہے۔
جب کسی client کو مسترد کیا جائے تو client کی رپورٹ دیکھیں
مسترد کیے گئے client کا دکھایا ہوا پیغام browser، curl کے version اور پس منظر میں استعمال ہونے والی TLS library پر منحصر ہوتا ہے۔ اس لیے اپنے client کے دکھائے ہوئے output کو دیکھیں، نہ کہ اسے کہیں درج کسی دوسرے پیغام سے ملائیں۔ مفید تفصیل server پر موجود ہوتی ہے۔
sudo tail -n 50 /var/log/nginx/error.logمسترد شدہ certificate کے نتیجے میں ایک ایسی line ظاہر ہوتی ہے جس میں client SSL certificate verify error شامل ہوتا ہے، جس کے بعد OpenSSL کی بتائی ہوئی وجہ لکھی ہوتی ہے۔ کارروائی کے لیے اسی وجہ کو بنیاد بنائیں۔ عموماً وجوہات چند ہی ہوتی ہیں۔ certificate، ssl_client_certificate میں درج file سے مختلف CA نے جاری کیا ہے۔ certificate اپنی validity dates سے باہر ہے۔ server پر موجود CRL اپنی nextUpdate سے گزر چکا ہے، اس لیے اب یہ صرف ایک client کے بجائے ہر client کو fail کر رہا ہے۔
جب browser کوئی certificate پیش ہی نہ کرے تو مسئلہ verification سے پہلے کے مرحلے میں ہوتا ہے۔ handshake کے دوران nginx قابل قبول issuer names بھیجتا ہے، لیکن browser کو اپنے store میں اس سے مطابقت رکھنے والا کچھ نہیں ملا، اس لیے اس کے پاس آپ کو پیش کرنے کے لیے کچھ نہیں تھا۔ .p12 دوبارہ اسی profile میں import کریں جس سے آپ واقعی browsing کر رہے ہیں۔
ایک اور صورت بھی قابل ذکر ہے۔ اگر آپ نے اپنے CA سے signed certificate کے بجائے اکیلا self-signed client certificate استعمال کرکے test کیا ہو تو verification کامیاب نہیں ہو سکتی، کیونکہ nginx signature کو CA file کے خلاف check کرتا ہے اور self-signed certificate اس file میں موجود نہیں ہوتا۔ certificate بنانے کا طریقہ وہی ہے جو Ubuntu پر self-signed certificate بنانے کے لیے استعمال ہوتا ہے۔ mTLS میں صرف ایک اضافی مرحلہ درکار ہے، جس میں آپ کا CA اسے sign کرتا ہے۔
FAQ
اگر میں mTLS استعمال کروں تو کیا مجھے پھر بھی Let's Encrypt certificate کی ضرورت ہے؟
جی ہاں۔ دونوں certificates کا آپس میں کوئی تعلق نہیں ہے۔ آپ کا server اپنا certificate پیش کرتا ہے تاکہ browser hostname پر اعتماد کرے، اور یہ certificate اب بھی ایسی CA سے حاصل ہونا چاہیے جسے browser پہلے سے جانتا ہو۔ آپ کی client CA ایک الگ private chain ہے، جو صرف یہ جانچنے کے لیے استعمال ہوتی ہے کہ کون connect کر رہا ہے۔ ssl_client_certificate مقرر کرنے سے nginx کے پیش کردہ certificate میں کوئی تبدیلی نہیں آتی، اور اسے آپ کی Let's Encrypt chain کی طرف نہیں ہونا چاہیے۔
میرا browser certificate منتخب کرنے کے لیے کبھی کیوں نہیں پوچھتا؟
nginx handshake کے دوران قابلِ قبول issuers کی ایک فہرست بھیجتا ہے، جو ssl_client_certificate میں موجود file سے تیار ہوتی ہے۔ Browser صرف وہ certificates پیش کرتا ہے جن کے issuers اس فہرست میں شامل ہوں۔ اس لیے prompt ظاہر نہ ہونے کا مطلب ہے کہ browser میں آپ کی CA کا کوئی certificate موجود نہیں: import کسی دوسرے browser profile میں ہوا ہے، یا certificate پر server میں installed CA سے مختلف CA نے دستخط کیے ہیں۔ openssl s_client -connect admin.example.com:443 چلائیں اور output میں قابلِ قبول client certificate CA names تلاش کریں تاکہ معلوم ہو سکے کہ server حقیقت میں کس CA کا مطالبہ کر رہا ہے۔
کیا میں صرف ایک URL پر client certificate لازمی قرار دے سکتا ہوں؟
location کے اندر ssl_verify_client on کے ساتھ نہیں۔ Certificate handshake کے دوران exchange ہوتا ہے، اس سے پہلے کہ nginx کو request path کا علم ہو، اور TLS 1.3 میں اس مقصد کے لیے درکار renegotiation ختم کر دی گئی ہے جبکہ HTTP/2 میں ممنوع ہے۔ server block میں ssl_verify_client optional; مقرر کریں، پھر ہر protected location میں $ssl_client_verify کی جانچ کریں اور جب وہ SUCCESS نہ ہو تو 403 واپس کریں۔
میں ایک شخص کی access کیسے منسوخ کروں؟
openssl ca -revoke کے ذریعے اس certificate کو revoke کریں، openssl ca -gencrl کے ذریعے فہرست دوبارہ تیار کریں، اسے server پر copy کریں، اور nginx reload کریں تاکہ وہ نئی file پڑھ سکے۔ باقی سب صارفین متاثر نہیں ہوں گے، لیکن یہ صرف اسی صورت میں درست ہے جب ہر شخص کے پاس اپنا certificate ہو، shared certificate نہ ہو۔ CRL کی nextUpdate تاریخ پر نظر رکھیں، کیونکہ expired CRL صرف revoked clients ہی نہیں بلکہ ہر client کے لیے verification ناکام کر دیتی ہے۔
کیا mTLS login page کی جگہ لے لیتا ہے؟
رسائی کے لحاظ سے ہاں: certificate کے بغیر کوئی درخواست application تک نہیں پہنچتی، اس لیے حملے کے لیے کوئی form اور اندازہ لگانے کے لیے کوئی password موجود نہیں ہوتا۔ Application کے اندر identity کے لحاظ سے نہیں۔ Certificate یہ ثابت کرتا ہے کہ caller کے پاس key file ہے، اس لیے چوری شدہ laptop رکھنے والا شخص valid user بن سکتا ہے۔ CN کو upstream بھیجیں، application میں پہلے سے موجود accounts اور permissions برقرار رکھیں، اور certificate کو ان کے سامنے موجود gate سمجھیں۔