সরাসরি পর্বে যান
পর্ব ৪৮ · ৬২-এর মধ্যে · মডিউল ৮

TLS আর HTTPS — handshake, certificate আর বিশ্বাসের শিকল

TLS and HTTPS — the TLS 1.3 Handshake, Key Schedule, X.509 Certificates, Chain of Trust, Revocation, Certificate Transparency & HSTS

Taught in Bangla, this lesson explains TLS and HTTPS: what TLS does and does not protect, the history from SSL to TLS 1.3, the TLS 1.3 handshake message by message, round trips, resumption, cipher suites and the record layer, X.509 certificates, the chain of trust and hostname validation, the Web PKI with CAs, revocation, Certificate Transparency and ACME, and HTTPS in practice with HSTS and mixed content.

এই পর্বে যা শিখবেন

  • TLS কী সুরক্ষা দেয় আর কী দেয় না, SSL থেকে TLS 1.3 পর্যন্ত ইতিহাস আর বিখ্যাত দুর্বলতাগুলোর শিক্ষা
  • TLS 1.3 handshake-এর প্রতিটি বার্তা, কোনটি কখন এনক্রিপ্টেড, key schedule, resumption, 0-RTT আর cipher suite পড়া
  • X.509 certificate-এর ঘর, chain of trust, path validation, hostname আর wildcard-এর নিয়ম — openssl দিয়ে নিজে বানিয়ে যাচাই
  • Web PKI: CA, DV/OV/EV, revocation, Certificate Transparency, ACME, মেয়াদ ছোট হওয়ার ধারা আর mTLS
  • HTTPS চালানো: HSTS, mixed content, TLS termination, ECH, TLS interception-এর ঝুঁকি, আর certificate-ত্রুটি সারানোর চেকলিস্ট

১ · TLS কী দেয় আর কী দেয় না — খামের ভেতরের চিঠি

IP, TCP, DNS, HTTP — এদের মূল নকশায় ডেটা যায় খোলা অবস্থায়; পথের router, একই Wi-Fi-র যে কেউ বা ISP পড়তে, এমনকি বদলাতেও পারে। পর্ব ৪৬-এ নিরাপত্তার তিন লক্ষ্য দেখেছেন: গোপনীয়তা, অখণ্ডতা আর প্রাপ্যতা (CIA). পর্ব ৪৭-এ দেখেছেন সেগুলোর গাণিতিক হাতিয়ার: symmetric এনক্রিপশন, hash, digital signature আর Diffie-Hellman. TLS (Transport Layer Security) হলো সেই হাতিয়ারগুলো জোড়া দিয়ে বানানো একটি protocol, যা TCP-র ওপরে বসে যেকোনো অ্যাপের বাইটকে সুরক্ষিত পাইপে পরিণত করে। আর HTTPS মানে শুধু “HTTP, কিন্তু TLS-এর ভেতর দিয়ে” — সাধারণত TCP port 443-এ (পর্ব ৩৬)।

ডাকঘরে পোস্টকার্ড পাঠালে পিয়ন থেকে বাছাইকারী সবাই লেখা পড়তে পারে — এটা সাধারণ HTTP. TLS হলো একটি মজবুত, সিলমোহর করা খাম, যার সিল ভাঙলেই ধরা পড়ে। সাথে প্রাপকের পরিচয় মেলানোর ব্যবস্থাও: পাঠানোর আগে আপনি তাঁর জাতীয় পরিচয়পত্র দেখে নেন, যা দিয়েছে এমন এক কর্তৃপক্ষ যাকে আপনি আগে থেকেই বিশ্বাস করেন। TLS-এ সেই পরিচয়পত্র হলো certificate, আর কর্তৃপক্ষ হলো CA. কিন্তু খেয়াল করুন: খামের ওপরের ঠিকানা, খামের আকার আর কখন পাঠালেন — এগুলো ডাকঘর দেখেই; খাম তো যেতে হবে।

TLS যা দেয়

গোপনীয়তা

Handshake-এ দুই পক্ষ নতুন, অস্থায়ী গোপন চাবি বানায়; তারপর সব ডেটা AEAD cipher (যেমন AES-GCM) দিয়ে এনক্রিপ্টেড। পথের লোক শুধু এলোমেলো বাইট দেখে।

confidentiality

অখণ্ডতা

প্রতিটি record-এর সাথে যাচাই-ট্যাগ; একটি bit বদলালে, record বাদ দিলে বা ক্রম বদলালেও ধরা পড়ে।

integrity

সার্ভারের পরিচয়

সার্ভার certificate দেখায় আর প্রমাণ করে যে তার private key তার কাছেই আছে। তাই মাঝখানে বসা কেউ (MITM) নিজেকে সার্ভার বলে চালাতে পারে না।

authentication

ক্লায়েন্টের পরিচয় (ঐচ্ছিক)

সার্ভার চাইলে ক্লায়েন্টের কাছেও certificate চাইতে পারে — mTLS. ওয়েবে বিরল, কিন্তু সার্ভিসে-সার্ভিসে আর ব্যাংকের API-তে সাধারণ।

client auth

TLS যা দেয় না

যা খোলা থাকেকেনকী দিয়ে কমানো যায়
উৎস ও গন্তব্যের IP ঠিকানা, portRouter-কে packet পৌঁছাতে হবে; TLS থাকে TCP-র ওপরেVPN বা Tor-এর মতো আরেক স্তর (পর্ব ৪৯)
সার্ভারের নাম (SNI)একই IP-তে অনেক সাইট; সার্ভারকে আগেই জানতে হয় কোন certificate দেবেECH (অংশ ৮); DNS-ও এনক্রিপ্ট করতে হয় (পর্ব ৩৫)
ডেটার আকার আর সময়এনক্রিপশন আকার প্রায় বদলায় নাpadding (TLS 1.3-এ সম্ভব, কিন্তু খুব কম ব্যবহার হয়)
দুই প্রান্তের যন্ত্রের নিরাপত্তাTLS শুধু পথ রক্ষা করে; ডেটা খোলা হয় প্রান্তেOS হালনাগাদ, malware থেকে সুরক্ষা, সার্ভারের নিরাপত্তা
সাইটটি সৎ কি নাcertificate শুধু বলে “এটা সত্যিই এই নামের সাইট”নাম নিজে মিলিয়ে দেখা; ফিশিং সাইটেও তালা থাকে

“তালা আছে, তাই নিরাপদ” — ভুল ধারণা। ব্রাউজারের তালা শুধু বলে যে আপনি ঠিকানাবারে লেখা নামের সার্ভারের সাথে এনক্রিপ্টেড পথে কথা বলছেন। examp1e-bank.com-এর মতো নকল নামের সাইটও বিনা খরচে বৈধ certificate পায়। তাই লেনদেনের আগে নামটাই আসল প্রশ্ন।

২ · SSL থেকে TLS 1.3 — ইতিহাস আর ভাঙনের শিক্ষা

১৯৯০-এর দশকের মাঝামাঝি Netscape ওয়েবে কেনাকাটা সম্ভব করতে SSL (Secure Sockets Layer) বানায়। SSL 1.0 প্রকাশই হয়নি, SSL 2.0-তে গুরুতর ত্রুটি ছিল, SSL 3.0 (১৯৯৬) টিকেছিল বহু বছর। এরপর IETF মানটি হাতে নিয়ে নাম দেয় TLS. আজও মানুষ “SSL certificate” বলে — কিন্তু SSL-এর সব সংস্করণ বহু আগেই নিষিদ্ধ।

সংস্করণসাল, দলিলআজকের অবস্থা
SSL 2.0 / 3.0১৯৯৫ / ১৯৯৬, Netscapeনিষিদ্ধ (RFC 6176, ২০১১; RFC 7568, ২০১৫)
TLS 1.0১৯৯৯, RFC 2246বাতিল (RFC 8996, ২০২১)
TLS 1.1২০০৬, RFC 4346বাতিল (RFC 8996, ২০২১)
TLS 1.2২০০৮, RFC 5246এখনো চলে; ঠিকমতো সাজালে (ECDHE + AEAD) নিরাপদ
TLS 1.3২০১৮, RFC 8446আধুনিক মান; QUIC-এর ভেতরেও এটিই (পর্ব ৩৩)

প্রধান ব্রাউজারগুলো ২০২০ সালের আশপাশে TLS 1.0 আর 1.1 সমর্থন তুলে নেয়; RFC 8996 (মার্চ ২০২১) সেটিকে আনুষ্ঠানিক রূপ দেয়। তাই খুব পুরোনো যন্ত্র আধুনিক সাইটে সংযোগই করতে পারে না।

বিখ্যাত ভাঙনগুলো — প্রতিটি একটি শিক্ষা

নিচের আক্রমণগুলো শুধু ধারণা হিসেবে, যাতে বোঝেন TLS 1.3 কেন এমন। প্রতিটির বিস্তারিত গবেষণাপত্রে আছে; সময় আর প্রভাব মোটামুটি হিসেবে দেওয়া।

নাম (প্রকাশ)মূল দুর্বলতা — ধারণাশিক্ষা ও প্রতিরক্ষা
BEAST (২০১১)TLS 1.0-এর CBC mode-এ পরের record-এর IV আগে থেকে অনুমান করা যেতTLS 1.1 থেকে প্রতি record-এ নতুন IV; শেষে CBC-ই বাদ
CRIME / BREACH (২০১২–১৩)Compression থেকে এনক্রিপ্ট করা দৈর্ঘ্যে গোপন তথ্য ফাঁস (পর্ব ৩৭)TLS-এ compression বন্ধ; TLS 1.3-এ নেই-ই
Heartbleed (২০১৪)protocol নয়, OpenSSL-এর কোডে ভুল: heartbeat বার্তায় দৈর্ঘ্য যাচাই না করায় সার্ভারের memory পড়া যেতছোট একটি bounds-check ভুলে private key পর্যন্ত ঝুঁকিতে; দ্রুত patch, key বদল, certificate নতুন করে নেওয়া
POODLE (২০১৪)SSL 3.0-এর CBC padding যাচাই না হওয়া; সাথে পুরোনো সংস্করণে নামিয়ে আনা (downgrade)SSL 3.0 বন্ধ; downgrade ঠেকানোর ব্যবস্থা
FREAK, Logjam (২০১৫)১৯৯০-এর দশকের রপ্তানি-নীতির দুর্বল (“export”) RSA ও DH বিকল্প তখনো চালু ছিল; সংযোগকে সেদিকে ঠেলে দেওয়া যেতদুর্বল বিকল্প পুরো মুছে ফেলা; ছোট DH group বাদ
মূল ধারণা

বেশিরভাগ ভাঙন এসেছে পুরোনো, ঐচ্ছিক বিকল্প থেকে, যেগুলো “সামঞ্জস্যের জন্য” চালু রাখা হয়েছিল। TLS 1.3-এর নকশা-দর্শন তাই: বিকল্প কমানো। Static RSA key exchange (forward secrecy নেই), CBC আর RC4, compression, renegotiation, নিজের বানানো DH group — সব বাদ। বাকি থাকল অল্প কয়েকটি শক্ত পছন্দ।

৩ · TLS 1.3 handshake — বার্তা ধরে ধরে

TCP সংযোগ হয়ে গেছে (পর্ব ২৯)। এখন ব্রাউজার www.example.com-এর সাথে TLS শুরু করবে। পুরো কাজের লক্ষ্য দুটি: (১) দুই পক্ষের হাতে এমন একটি গোপন সংখ্যা আসা যা পথের কেউ জানে না, আর (২) ক্লায়েন্টের নিশ্চিত হওয়া যে অপর প্রান্তে সত্যিই www.example.com. প্রথমটি আসে ephemeral Diffie-Hellman (সাধারণত X25519 curve-এ, তাই নাম ECDHE) থেকে, দ্বিতীয়টি certificate আর স্বাক্ষর থেকে। গণিত পর্ব ৪৭-এ (cryptography-basics.html); এখানে দেখব বার্তাগুলো কীভাবে সাজানো।

ClientHello — প্রথম বার্তায় কী কী থাকে

  • supported_versions: “আমি TLS 1.3 (0x0304) পারি”. পুরোনো legacy_version ঘরে ইচ্ছা করেই TLS 1.2-এর মান 0x0303 লেখা থাকে — কারণ নিচে।
  • cipher_suites: কোন AEAD আর hash পছন্দ, যেমন TLS_AES_128_GCM_SHA256.
  • key_share: ক্লায়েন্টের অস্থায়ী DH public key — আগে থেকেই, অনুমান করে যে সার্ভার X25519 নেবে। এখানেই এক RTT বাঁচে।
  • supported_groups, signature_algorithms: কোন curve আর কোন স্বাক্ষর-পদ্ধতি চলবে।
  • server_name (SNI): “আমি www.example.com চাই” — খোলা অবস্থায়।
  • ALPN: “h2 বা http/1.1” (ALPN, পর্ব ৩৭)।
  • random: ৩২ বাইটের এলোমেলো সংখ্যা, যাতে প্রতিটি handshake আলাদা হয়।

ServerHello-তে সার্ভার একটি cipher suite বেছে নেয় আর নিজের key_share পাঠায়। এই মুহূর্তে দুই পক্ষ একই ECDHE গোপন সংখ্যা হিসাব করতে পারে — আর তা থেকে handshake-এর চাবি। তাই ServerHello-র পরের সব কিছু এনক্রিপ্টেড, certificate-ও। TLS 1.2-এ certificate যেত খোলা অবস্থায়।

  1. ClientHellokey_share, SNIখোলা
  2. ServerHellokey_shareখোলা
  3. Certificate…EE, Cert, CVএনক্রিপ্টেড
  4. Finished × ২t = 1 RTTঅনুরোধ যায়
www.example.com-এর সাথে একটি নতুন TLS 1.3 সংযোগ
সময়
বার্তা
ভেতরে
পথের লোক দেখে
চাবি
  1. ClientHello — খোলা চিঠি। ক্লায়েন্ট সমর্থিত সংস্করণ (0x0304), cipher suite, X25519 public key, SNI আর ALPN একসাথে পাঠায়। এখনো কোনো চাবি নেই, তাই সব খোলা — পথের লোক জানে আপনি কোন সাইটে যাচ্ছেন।
  2. ServerHello — চাবির জন্ম। সার্ভার suite বেছে নিয়ে নিজের X25519 public key পাঠায়। এখন দুই পক্ষ নিজের private key আর অপরের public key থেকে একই গোপন সংখ্যা হিসাব করে, যা পথের লোক জানতে পারে না। তা থেকে বের হয় handshake traffic key.
  3. EncryptedExtensions — প্রথম এনক্রিপ্টেড বার্তা। ServerHello-তে শুধু চাবি তৈরির জন্য যা অপরিহার্য, বাকি উত্তর (যেমন বেছে নেওয়া ALPN = h2) এখানে, handshake key দিয়ে ঢাকা।
  4. Certificate — পরিচয়পত্র, তবে খামের ভেতরে। সার্ভার leaf certificate আর intermediate পাঠায় (root পাঠায় না — ক্লায়েন্টের কাছে আছেই)। TLS 1.3-এ এটিও এনক্রিপ্টেড, তাই কোন সাইটের certificate গেল তা পথের লোক দেখে না।
  5. CertificateVerify — “key আমার কাছেই আছে” প্রমাণ। Certificate তো প্রকাশ্য, যে কেউ কপি করতে পারে। তাই সার্ভার এ পর্যন্ত সব বার্তার hash-এ (transcript) leaf-এর private key দিয়ে স্বাক্ষর করে। ক্লায়েন্ট certificate-এর public key দিয়ে মিলিয়ে নেয়। এই স্বাক্ষরের হিসাবে দুই পক্ষের key share-ও অন্তর্ভুক্ত, তাই MITM নিজের key ঢুকিয়ে দিতে পারে না।
  6. সার্ভারের Finished. পুরো transcript-এর ওপর একটি HMAC — প্রমাণ যে সার্ভার একই বার্তাগুলো দেখেছে আর চাবি জানে। কেউ পথে একটি বার্তা বদলালে (যেমন suite-তালিকা থেকে শক্তটি মুছে) এখানে মিলবে না। সার্ভার এখন চাইলে application key দিয়ে ডেটা পাঠাতে পারে (“0.5-RTT”).
  7. এক RTT পূর্ণ — ক্লায়েন্টের Finished আর অনুরোধ। ক্লায়েন্ট certificate-এর শিকল, নাম, স্বাক্ষর আর Finished যাচাই করে নিজের Finished পাঠায়, আর একই সাথে application key দিয়ে প্রথম HTTP অনুরোধ। TCP-র ১ RTT ধরলে অনুরোধ যায় মোট ২ RTT পরে।
  8. NewSessionTicket — পরের বারের প্রস্তুতি। Handshake শেষে সার্ভার application key দিয়ে ঢাকা একটি ticket পাঠায়। পরের সংযোগে ক্লায়েন্ট এটি দেখিয়ে certificate ছাড়াই resumption করতে পারে, চাইলে 0-RTT-তে (অংশ ৪)।

নিজের যন্ত্রে একটি আসল handshake

Runner-এ openssl আছে কি না আগে দেখা হয়; থাকলে একটি অস্থায়ী self-signed certificate বানিয়ে 127.0.0.1-এ s_server চালানো হয়, আর s_client তার সাথে সংযোগ করে। -msg প্রতিটি record-এর header আর ভেতরের বার্তা দেখায়; নিচের awk সেটাকে সাজিয়ে দেখায় তারের ওপর কোন record type গেল আর ভেতরে আসলে কী ছিল। কোনো বাইরের সার্ভারে যাওয়া হয় না।

# A real TLS 1.3 handshake on loopback (127.0.0.1): openssl s_server <-> s_client.
command -v openssl >/dev/null || { echo "openssl নেই"; exit 0; }
cd "$(mktemp -d)" || exit 1
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout k.pem \
  -out c.pem -days 1 -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost" 2>/dev/null
openssl s_server -accept 127.0.0.1:8443 -cert c.pem -key k.pem -alpn h2,http/1.1 \
  -naccept 1 -quiet >srv.log 2>&1 &
sleep 1
echo hi | timeout 10 openssl s_client -connect 127.0.0.1:8443 -servername localhost \
  -alpn h2 -CAfile c.pem -verify_hostname localhost -msg >cli.log 2>&1
echo "== records on the wire (>>> client sends, <<< client receives) =="
awk 'BEGIN { n["16"]="handshake (plaintext)"; n["17"]="application_data (ENCRYPTED)"
             n["14"]="change_cipher_spec (compatibility only)"; n["15"]="alert" }
  /RecordHeader/ { d = $1; getline; print d, "record type", $1, n[$1]; next }
  /InnerContent/ { getline; if ($1 == "17") print "        inside: application data (hi)"; next }
  /^(<<<|>>>) TLS 1.3, (Handshake|Alert)/ { sub(/.*\], /, ""); print "        inside: " $0 }
' cli.log
echo "== summary =="
grep -E '^(New, |ALPN|Server Temp Key|Peer signature type|Verification:)' cli.log
wait

আউটপুট (এই runner-এ OpenSSL 3.0.13):

== records on the wire (>>> client sends, <<< client receives) ==
>>> record type 16 handshake (plaintext)
        inside: ClientHello
<<< record type 16 handshake (plaintext)
        inside: ServerHello
<<< record type 14 change_cipher_spec (compatibility only)
<<< record type 17 application_data (ENCRYPTED)
        inside: EncryptedExtensions
<<< record type 17 application_data (ENCRYPTED)
        inside: Certificate
<<< record type 17 application_data (ENCRYPTED)
        inside: CertificateVerify
<<< record type 17 application_data (ENCRYPTED)
        inside: Finished
>>> record type 14 change_cipher_spec (compatibility only)
>>> record type 17 application_data (ENCRYPTED)
        inside: Finished
>>> record type 17 application_data (ENCRYPTED)
        inside: application data (hi)
>>> record type 17 application_data (ENCRYPTED)
        inside: warning close_notify
== summary ==
Peer signature type: ECDSA
Server Temp Key: X25519, 253 bits
Verification: OK
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
ALPN protocol: h2

স্টেপারের সাথে হুবহু মেলে। প্রথম দুটি record-এর type 16 (দশমিকে ২২, handshake) — ClientHello আর ServerHello খোলা। তারপর সব record-এর type 17 (দশমিকে ২৩, application_data) — অথচ ভেতরে EncryptedExtensions, Certificate, CertificateVerify, Finished. TLS 1.3 আসল type এনক্রিপ্ট করা অংশের ভেতরে লুকায়। মাঝের change_cipher_spec record অর্থহীন — শুধু পুরোনো middlebox-কে “চেনা TLS 1.2” দেখানোর জন্য (ভাবনার প্রশ্ন ১)। আমাদের “hi” আর শেষের close_notify alert-ও type 17-এর ভেতরে। সারাংশে: TLSv1.3, suite TLS_AES_256_GCM_SHA384 (OpenSSL-এর পছন্দক্রমে AES-256 আগে), ALPN h2, key exchange X25519, স্বাক্ষর ECDSA (আমাদের P-256 key), যাচাই OK. অন্য যন্ত্রে বন্ধের আগে NewSessionTicket-ও দেখা যেতে পারে।

ClientHello-র বাইট পড়া

এবার উল্টো দিক থেকে: হাতে বানানো একটি ছোট ClientHello-র hex থেকে প্রতিটি ঘর পড়া — Wireshark যা করে, তার একটি ক্ষুদ্র রূপ। Random আর key-এর বাইট এখানে বানানো (আসলে এলোমেলো হয়)। শেষে একটি decoder cipher suite-এর লম্বা নাম ভেঙে দেখায়।

// A hand-built minimal TLS 1.3 ClientHello for www.example.com (random and key are fillers).
const hex = ["16 0301 00a0", "01 00009c", "0303", "11".repeat(32), "00", "0006 1301 1302 1303",
  "01 00", "006d", "0000 0014 0012 00 000f 7777772e6578616d706c652e636f6d",
  "000a 0004 0002 001d", "000d 0006 0004 0403 0804", "0010 000e 000c 02 6832 08 687474702f312e31",
  "002b 0003 02 0304", "0033 0026 0024 001d 0020" + "ab".repeat(32)].join("").replace(/ /g, "");
const b = hex.match(/../g).map(h => parseInt(h, 16)); let i = 0;
const u = (n) => { let v = 0; for (let k = 0; k < n; k++) v = v * 256 + b[i++]; return v; };
const x = (v) => "0x" + v.toString(16).padStart(4, "0");
const u16s = (d) => d.filter((_, k) => k % 2 === 0).map((hi, k) => x(hi * 256 + d[2 * k + 1])).join(" ");
const SUITE = { 0x1301: "TLS_AES_128_GCM_SHA256", 0x1302: "TLS_AES_256_GCM_SHA384", 0x1303: "TLS_CHACHA20_POLY1305_SHA256" };
const EXT = { 0: "server_name", 10: "supported_groups", 13: "signature_algorithms", 16: "alpn", 43: "supported_versions", 51: "key_share" };
console.log(`total ${b.length} bytes`);
console.log(`record   : type ${u(1)} (22 = handshake), version ${x(u(2))}, length ${u(2)}`);
console.log(`handshake: type ${u(1)} (1 = ClientHello), length ${u(3)}`);
console.log(`legacy_version ${x(u(2))}, random ${hex.substr(2 * i, 8)}... (32 bytes)`); i += 32;
const sid = u(1); i += sid; console.log(`legacy_session_id: ${sid} bytes`);
for (let end = u(2) + i; i < end; ) { const s = u(2); console.log(`  cipher suite ${x(s)} ${SUITE[s]}`); }
i += 1 + b[i]; console.log("compression: null only");
for (let end = u(2) + i; i < end; ) {
  const t = u(2), len = u(2), d = b.slice(i, i + len); i += len;
  const info = t === 0 ? "host = " + String.fromCharCode(...d.slice(5))
    : t === 43 ? u16s(d.slice(1)) + " (= TLS 1.3)"
    : t === 51 ? `group ${x(d[2] * 256 + d[3])} (x25519), public key ${d[4] * 256 + d[5]} bytes`
    : t === 16 ? String.fromCharCode(...d.slice(2)).replace(/[\x00-\x1f]/g, " ").trim() : u16s(d.slice(2));
  console.log(`  ext ${x(t)} ${EXT[t].padEnd(20)} ${String(len).padStart(2)} B: ${info}`);
}
console.log(i === b.length ? "parsed every byte" : `left over: ${b.length - i}`);
function decode(name) {                      // read a cipher-suite name piece by piece
  const [, kxAuth, cipher, hash] = name.match(/^TLS_(?:(\w+?)_WITH_)?(\w+)_(SHA\d*)$/);
  if (!kxAuth) return `${name}\n    TLS 1.3: AEAD ${cipher}, hash ${hash} (for HKDF); key exchange + signature chosen separately`;
  const [kx, auth = kx] = kxAuth.split("_");
  return `${name}\n    TLS 1.2: key exchange ${kx}${kx.startsWith("ECDHE") || kx.startsWith("DHE") ? " (forward secrecy)" : " (NO forward secrecy)"}, ` +
         `auth ${auth}, cipher ${cipher}, hash ${hash}`;
}
["TLS_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA"].forEach(n => console.log(decode(n)));

আউটপুট:

total 165 bytes
record   : type 22 (22 = handshake), version 0x0301, length 160
handshake: type 1 (1 = ClientHello), length 156
legacy_version 0x0303, random 11111111... (32 bytes)
legacy_session_id: 0 bytes
  cipher suite 0x1301 TLS_AES_128_GCM_SHA256
  cipher suite 0x1302 TLS_AES_256_GCM_SHA384
  cipher suite 0x1303 TLS_CHACHA20_POLY1305_SHA256
compression: null only
  ext 0x0000 server_name          20 B: host = www.example.com
  ext 0x000a supported_groups      4 B: 0x001d
  ext 0x000d signature_algorithms  6 B: 0x0403 0x0804
  ext 0x0010 alpn                 14 B: h2 http/1.1
  ext 0x002b supported_versions    3 B: 0x0304 (= TLS 1.3)
  ext 0x0033 key_share            38 B: group 0x001d (x25519), public key 32 bytes
parsed every byte
TLS_AES_128_GCM_SHA256
    TLS 1.3: AEAD AES_128_GCM, hash SHA256 (for HKDF); key exchange + signature chosen separately
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    TLS 1.2: key exchange ECDHE (forward secrecy), auth RSA, cipher AES_128_GCM, hash SHA256
TLS_RSA_WITH_AES_128_CBC_SHA
    TLS 1.2: key exchange RSA (NO forward secrecy), auth RSA, cipher AES_128_CBC, hash SHA

পাঁচ বাইটের record header (16 0301 00a0): type ২২, পুরোনো version 0x0301, দৈর্ঘ্য ১৬০. তারপর handshake header: type ১ = ClientHello, দৈর্ঘ্য ১৫৬ (৩ বাইটে)। ৫ + ৪ + ১৫৬ = ১৬৫ — “parsed every byte” নিশ্চিত করে যে দৈর্ঘ্যের সব ঘর ঠিক। TLS 1.3-এর আসল সংকেত শুধু supported_versions-এ (0x0304); legacy_version 0x0303 থাকে পুরোনো middlebox-এর জন্য। Key share-এ X25519 (0x001d) আর ৩২ বাইটের public key. ওপরের openssl-এর আসল ClientHello ছিল ৩১৫ বাইট (0x013b) — TLS 1.2-এর suite, ৩২ বাইটের session id আর আরও extension থাকায়। Decoder-এর ব্যাখ্যা অংশ ৪-এ।

Key schedule — একটি গোপন সংখ্যা থেকে অনেক চাবি

ECDHE থেকে পাওয়া একটি গোপন সংখ্যা দিয়েই সব কাজ চালানো বিপজ্জনক: এক জায়গার দুর্বলতা সবখানে ছড়াবে। তাই TLS 1.3 HKDF (HMAC-ভিত্তিক key derivation, RFC 5869) দিয়ে একটি key schedule চালায়, মোটামুটি তিন ধাপে:

  1. Early secret — resumption-এর PSK থাকলে তা থেকে (0-RTT-এর চাবি এখান থেকে), না থাকলে শূন্য থেকে।
  2. Handshake secret — ECDHE-র গোপন সংখ্যা মিশিয়ে। এখান থেকে দুই দিকের আলাদা handshake traffic key, ClientHello…ServerHello-র hash-এর সাথে বাঁধা।
  3. Master secret — এখান থেকে দুই দিকের application traffic key (সার্ভারের Finished পর্যন্ত transcript-এ বাঁধা), আর resumption-এর গোপন সংখ্যা।

প্রতিটি চাবি নির্দিষ্ট লেবেল (“c hs traffic”, “s ap traffic” ইত্যাদি) আর transcript hash দিয়ে আলাদা; দুই দিকের চাবি কখনো এক নয়, আর দীর্ঘ সংযোগে KeyUpdate দিয়ে application key নতুন করা যায়। আর যেহেতু DH key প্রতি সংযোগে নতুন আর কাজ শেষে মুছে ফেলা হয়, সার্ভারের certificate key পরে চুরি হলেও আগের রেকর্ড করা আলাপ খোলা যায় না — এটাই forward secrecy.

৪ · RTT, resumption, cipher suite আর record layer

TLS 1.2-এ কেন ২ RTT লাগত

TLS 1.2-এর পূর্ণ handshake: ক্লায়েন্ট ClientHello পাঠায় (কোনো key share ছাড়া) → সার্ভার ServerHello, Certificate, ServerKeyExchange, ServerHelloDone পাঠায় → ক্লায়েন্ট ClientKeyExchange, ChangeCipherSpec, Finished পাঠায় → সার্ভার ChangeCipherSpec, Finished পাঠায়। ক্লায়েন্ট key share পাঠাতে পারে সার্ভারের পছন্দ জানার পরে, তাই এক RTT বেশি। TLS 1.3 অনুমান করে প্রথমেই key share দেয়; অনুমান ভুল হলে সার্ভার HelloRetryRequest পাঠায় আর এক RTT বাড়ে — বাস্তবে বিরল। পর্ব ৩৩-এর ক্যালকুলেটর অনুযায়ী নতুন সংযোগে প্রথম বাইট পর্যন্ত: TCP + TLS 1.2 = ৪ RTT, TCP + TLS 1.3 = ৩ RTT.

Resumption আর 0-RTT

NewSessionTicket-এ পাওয়া ticket পরের বার ClientHello-তে pre_shared_key extension হিসেবে যায় — একে বলে session resumption. দুটি ধরন আছে: শুধু PSK (দ্রুত, কিন্তু forward secrecy নেই), আর PSK + নতুন ECDHE (psk_dhe_ke, ব্রাউজারে সাধারণ)। Resumption-এ certificate আর স্বাক্ষর লাগে না, তাই CPU আর বাইট বাঁচে। এর ওপর ক্লায়েন্ট চাইলে প্রথম বার্তার সাথেই early data (0-RTT) পাঠাতে পারে — কিন্তু সেই ডেটা replay হতে পারে, তাই শুধু idempotent অনুরোধে। ঝুঁকি আর প্রতিরক্ষা (425 Too Early ইত্যাদি) পর্ব ৩৩-এ বিস্তারিত; এখানে আবার নয়।

Cipher suite — নাম পড়তে শেখা

TLS 1.2-এর নামে চারটি সিদ্ধান্ত একসাথে লেখা থাকত। TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ভাঙলে:

অংশঅর্থ
ECDHEkey exchange: elliptic-curve ephemeral Diffie-Hellman — forward secrecy আছে
RSAসার্ভারের পরিচয়-প্রমাণ: certificate-এ RSA key, তা দিয়ে স্বাক্ষর
AES_128_GCMডেটার এনক্রিপশন: ১২৮-bit key-এর AES, GCM mode (AEAD)
SHA256handshake-এর PRF-এ ব্যবহৃত hash

TLS 1.3 নাম ছোট করেছে: TLS_AES_128_GCM_SHA256 শুধু AEAD আর HKDF-এর hash বলে; key exchange আর স্বাক্ষর আলাদা extension-এ (supported_groups, signature_algorithms) ঠিক হয়। RFC 8446-এ মোট পাঁচটি suite: 0x1301 TLS_AES_128_GCM_SHA256 (সব বাস্তবায়নে বাধ্যতামূলক), 0x1302 TLS_AES_256_GCM_SHA384, 0x1303 TLS_CHACHA20_POLY1305_SHA256 (AES-এর hardware সহায়তা নেই এমন ফোনে দ্রুত), আর কম ব্যবহৃত দুটি CCM suite (0x1304, 0x1305). অংশ ৩-এর decoder-এর শেষ লাইনটি দেখুন: TLS_RSA_WITH_AES_128_CBC_SHA-এ key exchange-ই RSA — সার্ভারের দীর্ঘমেয়াদি key দিয়ে গোপন সংখ্যা পাঠানো, তাই forward secrecy নেই, আর “SHA” মানে SHA-1-ভিত্তিক HMAC; TLS 1.3 এর কোনোটিই রাখেনি।

Record layer — খামের মাপ

Handshake-এর পর সব ডেটা যায় record-এ। প্রতিটি record-এর ৫ বাইটের header: content type (২০ change_cipher_spec, ২১ alert, ২২ handshake, ২৩ application_data), পুরোনো version (0x0303) আর দৈর্ঘ্য। একটি record-এ সর্বোচ্চ ২১৪ = ১৬,৩৮৪ বাইট মূল ডেটা। TLS 1.3-এ এনক্রিপ্ট করা record-এর ভেতরে থাকে ডেটা + আসল content type (১ বাইট) + ঐচ্ছিক শূন্য-padding, আর শেষে AEAD-এর ১৬ বাইটের যাচাই-ট্যাগ। প্রতিটি record-এর nonce আসে একটি ৬৪-bit record নম্বর আর একটি স্থির IV-এর XOR থেকে — নম্বর কোথাও পাঠানো হয় না, দুই পক্ষ গুনে রাখে; তাই record বাদ দেওয়া বা ক্রম বদলালে যাচাই ব্যর্থ হয়।

একটি হিসাব: AES-GCM-এ পূর্ণ একটি record-এর বাড়তি খরচ = ৫ (header) + ১ (ভেতরের type) + ১৬ (ট্যাগ) = ২২ বাইট। কার্যকারিতা = ১৬৩৮৪ ÷ (১৬৩৮৪ + ২২) ≈ ৯৯.৮৭%. ১ MiB (১০,৪৮,৫৭৬ বাইট) ফাইল পাঠাতে ১০৪৮৫৭৬ ÷ ১৬৩৮৪ = ৬৪টি record, বাড়তি ৬৪ × ২২ = ১,৪০৮ বাইট — প্রায় ০.১৩%. অর্থাৎ এনক্রিপশনের আসল খরচ bandwidth নয়, handshake-এর RTT আর CPU; আর আধুনিক CPU-তে AES-এর hardware নির্দেশনা থাকায় সেটিও সামান্য।

৫ · Certificate আর X.509 — ডিজিটাল পরিচয়পত্র

সার্ভারের public key তো যে কেউ বানাতে পারে। ক্লায়েন্ট কীভাবে জানবে “এই key-টিই www.example.com-এর”? উত্তর: একটি বিশ্বস্ত তৃতীয় পক্ষ — CA — সেই দাবিতে স্বাক্ষর করে। স্বাক্ষর করা এই দলিলের মান-আকার X.509 (সংস্করণ ৩; ইন্টারনেটের নিয়ম RFC 5280-এ)। প্রধান ঘরগুলো:

ঘরউদাহরণমানে
SubjectCN = www.example.comcertificate কার; ওয়েবে এখন শুধু তথ্য হিসেবে
IssuerCN = Demo CA 1কে স্বাক্ষর করেছে — শিকলের পরের কড়া
ValiditynotBefore … notAfterকোন সময়ের মধ্যে বৈধ — ঘড়ি ভুল হলে এখানেই ব্যর্থ
Subject Public KeyEC P-256 বা RSA 2048সার্ভারের public key — CertificateVerify এটি দিয়েই মেলানো হয়
Subject Alternative Name (SAN)DNS:www.example.com, DNS:example.comকোন কোন নামে বৈধ — নাম মেলানো হয় শুধু এখানে
Key Usage / Extended Key UsagedigitalSignature; serverAuthkey কোন কাজে ব্যবহার করা যাবে
Basic ConstraintsCA:FALSE (leaf), CA:TRUE (CA)এই certificate অন্য certificate-এ স্বাক্ষর করতে পারে কি না
Key ID, CRL/OCSP ঠিকানা, SCTAuthority Key Identifier ইত্যাদিশিকল খোঁজা, revocation আর CT-র তথ্য (অংশ ৭)
Signatureecdsa-with-SHA256ওপরের সব ঘরের hash-এ issuer-এর স্বাক্ষর

CN পুরোনো রীতি। একসময় Subject-এর CN (Common Name) দিয়ে নাম মেলানো হতো। মানগুলো (RFC 2818, পরে RFC 6125) বহু আগেই SAN-কে প্রধান বলেছে, আর প্রধান ব্রাউজারগুলো ২০১৭ সালের আশপাশ থেকে CN দেখা পুরো বন্ধ করেছে। তাই SAN ছাড়া certificate বানালে ব্রাউজার name mismatch দেখাবে — নিজের ল্যাবে certificate বানানোর সময় সবচেয়ে সাধারণ ভুল এটি।

নিজে একটি ছোট PKI বানানো

নিচের script একটি অস্থায়ী “Lab Root CA” বানায় (self-signed, CA:TRUE), তারপর localhost-এর জন্য একটি key আর CSR (certificate চাওয়ার আবেদন) বানিয়ে CA দিয়ে স্বাক্ষর করায় — SAN-সহ। শেষে চারভাবে যাচাই। পুরো কাজ একটি অস্থায়ী ফোল্ডারে, কোনো বাইরের সংযোগ নেই; -subj দেওয়ায় কোনো প্রশ্নও করে না।

# A throwaway lab CA and a "localhost" certificate - nothing here touches the internet.
command -v openssl >/dev/null || { echo "openssl নেই - এই runner-এ চালানো যাবে না"; exit 0; }
openssl version
cd "$(mktemp -d)" || exit 1
newca() {  # $1 = file prefix, $2 = CA name: self-signed, CA:TRUE, may sign certificates
  openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
    -keyout "$1.key" -out "$1.crt" -days 30 -subj "/CN=$2" \
    -addext "basicConstraints=critical,CA:TRUE" \
    -addext "keyUsage=critical,keyCertSign,cRLSign" 2>/dev/null
}
newca labca "Lab Root CA"
newca otherca "Some Other CA"

# leaf key + certificate signing request, then the lab CA signs it (with SAN)
openssl req -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout leaf.key -out leaf.csr -subj "/CN=localhost" 2>/dev/null
printf '%s\n' "subjectAltName=DNS:localhost,IP:127.0.0.1" "basicConstraints=CA:FALSE" \
  "extendedKeyUsage=serverAuth" > leaf.ext
openssl x509 -req -in leaf.csr -CA labca.crt -CAkey labca.key -CAcreateserial \
  -days 7 -extfile leaf.ext -out leaf.crt 2>/dev/null

echo "== leaf certificate =="
openssl x509 -in leaf.crt -noout -subject -issuer -dates -ext subjectAltName,basicConstraints
echo "== checks =="
chk() { echo "--- $1"; shift; openssl verify "$@" leaf.crt 2>&1 | head -2; }
chk "trusted lab CA"                  -CAfile labca.crt
chk "lab CA + name localhost"         -CAfile labca.crt -verify_hostname localhost
chk "lab CA + name www.example.com"   -CAfile labca.crt -verify_hostname www.example.com
chk "only the OTHER CA is trusted"    -CAfile otherca.crt
echo "done"

আউটপুট (তারিখগুলো চালানোর দিন অনুযায়ী বদলাবে):

OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)
== leaf certificate ==
subject=CN = localhost
issuer=CN = Lab Root CA
notBefore=Oct 26 17:44:14 2026 GMT
notAfter=Nov  2 17:44:14 2026 GMT
X509v3 Subject Alternative Name: 
    DNS:localhost, IP Address:127.0.0.1
X509v3 Basic Constraints: 
    CA:FALSE
== checks ==
--- trusted lab CA
leaf.crt: OK
--- lab CA + name localhost
leaf.crt: OK
--- lab CA + name www.example.com
CN = localhost
error 62 at 0 depth lookup: hostname mismatch
--- only the OTHER CA is trusted
CN = localhost
error 20 at 0 depth lookup: unable to get local issuer certificate
done

প্রথম অংশে leaf-এর মূল ঘর: subject localhost, issuer আমাদের “Lab Root CA”, মেয়াদ ঠিক ৭ দিন (-days 7), SAN-এ DNS নাম আর IP দুটোই, আর CA:FALSE — এটি দিয়ে আর কোনো certificate স্বাক্ষর করা যাবে না। এরপর চার যাচাই: (১) lab CA-কে বিশ্বাস করলে OK; (২) নাম localhost চাইলেও OK — SAN-এ আছে; (৩) www.example.com চাইলে error 62 … hostname mismatch — শিকল ঠিক, কিন্তু নাম মেলে না; (৪) শুধু অন্য একটি CA-কে বিশ্বাস করলে error 20 … unable to get local issuer certificate — যাচাইকারী leaf-এর issuer খুঁজে পায়নি। ব্রাউজারে এই দুটিই “name mismatch” আর “unknown issuer” ত্রুটি।

৬ · বিশ্বাসের শিকল — chain, path validation আর hostname

ব্রাউজার বা OS সাথে নিয়ে আসে কয়েকশো root certificate-এর একটি তালিকা — trust store. Windows, macOS, Android-এর নিজস্ব তালিকা আছে; Firefox রাখে নিজের (Mozilla-র) তালিকা; Linux-এ সাধারণত ca-certificates প্যাকেজ। সার্ভারের leaf certificate-এ সরাসরি root স্বাক্ষর করে না; মাঝখানে থাকে এক বা একাধিক intermediate certificate. পুরো সারিটিই certificate chain: leaf → intermediate → root.

একটি বাণিজ্যিক ব্যাংকের শাখা-ব্যবস্থাপকের সই করা চিঠি ভাবুন। আপনি ব্যবস্থাপককে চেনেন না, কিন্তু চিঠির সাথে আছে প্রধান কার্যালয়ের সই করা নিয়োগপত্র, আর প্রধান কার্যালয়ের লাইসেন্স দিয়েছে বাংলাদেশ ব্যাংক — যাকে আপনি আগে থেকেই বিশ্বাস করেন। প্রতিটি কড়া পরেরটির সইয়ে টিকে আছে। একটি কড়া হারালে (নিয়োগপত্র নেই) বা মেয়াদোত্তীর্ণ হলে পুরো শিকল অচল।

Intermediate কেন?

  • Root-এর key অফলাইনে: root key থাকে নিরাপদ hardware-এ (HSM), প্রায় সবসময় বন্ধ; প্রতিদিনের স্বাক্ষর করে intermediate.
  • ক্ষতি সীমিত: intermediate-এর key ফাঁস হলে শুধু সেটি বাতিল; root বদলাতে কোটি যন্ত্রের trust store হালনাগাদ লাগত — বছরের কাজ।
  • নমনীয়তা: ভিন্ন কাজে ভিন্ন intermediate; নতুন root-কে পুরোনো root দিয়ে cross-sign করে পুরোনো যন্ত্রেও চালানো।

নিয়ম: সার্ভার leaf আর সব intermediate পাঠাবে, root নয়। Intermediate পাঠাতে ভুলে গেলে অনেক ব্রাউজার আগের ভিজিটের cache থেকে বা certificate-এ লেখা ঠিকানা থেকে নামিয়ে নিয়ে কাজ চালায় — কিন্তু curl, মোবাইল অ্যাপ আর অন্য সার্ভার ব্যর্থ হয়। “ব্রাউজারে চলে, API-তে চলে না” — এর সবচেয়ে সাধারণ কারণ এটি।

  1. Leafwww.example.comissuer: Demo CA 1
  2. IntermediateDemo CA 1issuer: Demo Root R1
  3. RootDemo Root R1trust store-এ
  4. বিশ্বস্ততালাসংযোগ চলবে
সময় 2026-10-26, নাম www.example.com
ধাপ
কী দেখা হলো
ফল
  1. সার্ভার যা পাঠাল। Certificate বার্তায় দুটি certificate: www.example.com-এর leaf আর তার issuer “Demo CA 1”. Root পাঠানো হয়নি — পাঠালেও লাভ নেই, কারণ বিশ্বাস আসে শুধু নিজের trust store থেকে।
  2. পথ বানানো (path building). Leaf-এর issuer নাম আর Authority Key ID দিয়ে পাঠানো তালিকায় Demo CA 1 পাওয়া গেল। তার issuer “Demo Root R1” — trust store-এ আছে। পথ সম্পূর্ণ। (একাধিক সম্ভাব্য পথ থাকলে যাচাইকারী বিকল্পও চেষ্টা করে।)
  3. স্বাক্ষর মেলানো। প্রতিটি কড়ার স্বাক্ষর তার ওপরের কড়ার public key দিয়ে যাচাই। Leaf-এর স্বাক্ষর Demo CA 1-এর key দিয়ে, Demo CA 1-এর স্বাক্ষর root-এর key দিয়ে। কোথাও একটি bit বদলালে এখানেই ব্যর্থ।
  4. মেয়াদ। আজকের তারিখ (যন্ত্রের ঘড়ি অনুযায়ী!) প্রতিটির notBefore আর notAfter-এর মধ্যে কি না। Leaf নভেম্বরের শেষে শেষ হবে, intermediate ২০২৯-এ — দুটিই বৈধ।
  5. সীমাবদ্ধতা। যে certificate অন্যকে স্বাক্ষর করেছে, তার Basic Constraints-এ CA:TRUE আর Key Usage-এ keyCertSign থাকতেই হবে; path length আর name constraints থাকলে সেগুলোও। Leaf-এর EKU-তে serverAuth.
  6. নাম। ঠিকানাবারের নাম www.example.com leaf-এর SAN-তালিকায় আছে। CN দেখা হয় না।
  7. নীতি আর ফল। এরপর revocation দেখা (OCSP stapling থাকলে সার্ভার নিজেই উত্তর দিয়েছে) আর ব্রাউজারের নীতি — যেমন Certificate Transparency-র প্রমাণ (SCT) আছে কি না। এই অংশ ব্রাউজারভেদে ভিন্ন। সব ঠিক থাকলে তালা, আর handshake এগোয় CertificateVerify-তে।

Hostname আর wildcard-এর নিয়ম

  • নাম মেলানো ছোট-বড় হাতের অক্ষর না দেখে; শুধু SAN থেকে।
  • *.example.com মেলে api.example.com-এর সাথে — ঠিক একটি বাম-দিকের label.
  • মেলে না a.b.example.com (দুই label) বা খোদ example.com (শূন্য label) — তাই অনেক certificate-এ দুটিই রাখা হয়: example.com আর *.example.com.
  • Wildcard শুধু সবচেয়ে বামের পুরো label-এ; CA-রা *.com-এর মতো পুরো public suffix-এর wildcard দেয় না।
  • IP ঠিকানায় সংযোগ করলে SAN-এ “IP Address” ধরনের এন্ট্রি লাগে (ওপরের lab certificate-এ যেমন 127.0.0.1).

এবার এই সব যাচাই একটি simulator-এ, ডেটার রেকর্ড দিয়ে। আসল স্বাক্ষর-গণিতের বদলে key ID মেলানো হয়েছে; বাকি ক্রম ব্রাউজারের মতোই। আটটি শিকল — একটি ঠিক, বাকিগুলো একেকভাবে ভাঙা।

// Toy certificate-path validator on plain data records (a matching key ID stands in for the signature check).
const TRUST_STORE = [{ subject: "Demo Root R1", ski: "R1" }], NOW = new Date("2026-10-26T12:00:00Z");
const leaf  = (o) => ({ subject: "www.example.com", issuer: "Demo CA 1", aki: "I1", ca: false,
  san: ["www.example.com", "example.com"], from: "2026-09-01", to: "2026-11-29", ...o });
const inter = (o) => ({ subject: "Demo CA 1", issuer: "Demo Root R1", ski: "I1", aki: "R1",
  ca: true, from: "2024-01-01", to: "2029-01-01", ...o });
const nameMatches = (host, pat, dot = host.indexOf(".")) =>   // "*." covers exactly ONE leftmost label
  pat.startsWith("*.") ? dot > 0 && host.slice(dot + 1) === pat.slice(2) : host === pat;
function validate(host, chain) {
  const say = (good, msg) => { console.log((good ? "   ok   " : "   FAIL ") + msg); return good; }, [lf, ...sent] = chain;
  if (!say(lf.san.some(p => nameMatches(host, p)), `${host} matches SAN [${lf.san.join(", ")}]`)) return;
  for (let cur = lf, isLeaf = true; ; isLeaf = false) {
    if (!say(new Date(cur.from) <= NOW && NOW <= new Date(cur.to + "T23:59:59Z"), `"${cur.subject}" valid ${cur.from} .. ${cur.to}`)) return;
    if (!isLeaf && !say(cur.ca, `"${cur.subject}" is a CA (basicConstraints CA:TRUE)`)) return;
    if (TRUST_STORE.some(r => r.subject === cur.issuer && r.ski === cur.aki))
      return say(true, `issuer "${cur.issuer}" is in the trust store  => TRUSTED`);
    const up = sent.find(c => c.subject === cur.issuer && c.ski === cur.aki);
    if (!say(up && up.subject !== up.issuer, `issuer "${cur.issuer}" ` +
        (!up ? "not sent by server and not in trust store" : up.subject === up.issuer ?
         "is a self-signed root that is NOT in the trust store" : "found in the chain the server sent"))) return;
    cur = up;
  }
}
const selfRoot = { subject: "Home Lab CA", issuer: "Home Lab CA", ski: "H1", aki: "H1", ca: true, from: "2026-01-01", to: "2036-01-01" };
const cases = [["good chain", "www.example.com", [leaf(), inter()]],
  ["expired leaf", "www.example.com", [leaf({ to: "2026-10-20" }), inter()]],
  ["wrong name", "shop.example.com", [leaf(), inter()]],
  ["wildcard, one label", "api.example.com", [leaf({ san: ["*.example.com"] }), inter()]],
  ["wildcard, two labels", "a.b.example.com", [leaf({ san: ["*.example.com"] }), inter()]],
  ["missing intermediate", "www.example.com", [leaf()]],
  ["untrusted root", "www.example.com", [leaf({ issuer: "Home Lab CA", aki: "H1" }), selfRoot]],
  ["non-CA as intermediate", "www.example.com", [leaf(), inter({ ca: false })]]];
for (const [label, host, chain] of cases) { console.log(`## ${label}`); validate(host, chain); }

আউটপুট:

## good chain
   ok   www.example.com matches SAN [www.example.com, example.com]
   ok   "www.example.com" valid 2026-09-01 .. 2026-11-29
   ok   issuer "Demo CA 1" found in the chain the server sent
   ok   "Demo CA 1" valid 2024-01-01 .. 2029-01-01
   ok   "Demo CA 1" is a CA (basicConstraints CA:TRUE)
   ok   issuer "Demo Root R1" is in the trust store  => TRUSTED
## expired leaf
   ok   www.example.com matches SAN [www.example.com, example.com]
   FAIL "www.example.com" valid 2026-09-01 .. 2026-10-20
## wrong name
   FAIL shop.example.com matches SAN [www.example.com, example.com]
## wildcard, one label
   ok   api.example.com matches SAN [*.example.com]
   ok   "www.example.com" valid 2026-09-01 .. 2026-11-29
   ok   issuer "Demo CA 1" found in the chain the server sent
   ok   "Demo CA 1" valid 2024-01-01 .. 2029-01-01
   ok   "Demo CA 1" is a CA (basicConstraints CA:TRUE)
   ok   issuer "Demo Root R1" is in the trust store  => TRUSTED
## wildcard, two labels
   FAIL a.b.example.com matches SAN [*.example.com]
## missing intermediate
   ok   www.example.com matches SAN [www.example.com, example.com]
   ok   "www.example.com" valid 2026-09-01 .. 2026-11-29
   FAIL issuer "Demo CA 1" not sent by server and not in trust store
## untrusted root
   ok   www.example.com matches SAN [www.example.com, example.com]
   ok   "www.example.com" valid 2026-09-01 .. 2026-11-29
   FAIL issuer "Home Lab CA" is a self-signed root that is NOT in the trust store
## non-CA as intermediate
   ok   www.example.com matches SAN [www.example.com, example.com]
   ok   "www.example.com" valid 2026-09-01 .. 2026-11-29
   ok   issuer "Demo CA 1" found in the chain the server sent
   ok   "Demo CA 1" valid 2024-01-01 .. 2029-01-01
   FAIL "Demo CA 1" is a CA (basicConstraints CA:TRUE)

“good chain” স্টেপারের সাথে মেলে: নাম, leaf-এর মেয়াদ, intermediate পাওয়া, তার মেয়াদ, CA:TRUE, তারপর root trust store-এ — TRUSTED. বাকিগুলো একেকটি বাস্তব ত্রুটি: expired leaf — ২০ অক্টোবর শেষ, আজ ২৬; wrong name — shop.example.com SAN-এ নেই; wildcard — api.example.com মেলে, কিন্তু a.b.example.com মেলে না (এক label-এর নিয়ম); missing intermediate — সার্ভার শুধু leaf পাঠিয়েছে, “Demo CA 1” কোথাও নেই; untrusted root — শিকল শেষ হয়েছে একটি self-signed root-এ, যা trust store-এ নেই (নিজের বানানো CA বা interception যন্ত্রের CA-র ক্ষেত্রে ঠিক এটাই ঘটে); non-CA as intermediate — যে certificate স্বাক্ষর করেছে তার CA:TRUE নেই। শেষটি ইতিহাসে গুরুত্বপূর্ণ: ২০০০-এর দশকে কিছু যাচাইকারী Basic Constraints দেখত না, ফলে যে কেউ নিজের সাধারণ leaf দিয়ে অন্য সাইটের certificate “স্বাক্ষর” করতে পারত।

ত্রুটির বার্তা দেখে কারণ খোঁজা

যা দেখবেনসম্ভাব্য কারণপ্রথমে যা করবেন
Chrome NET::ERR_CERT_DATE_INVALID, Firefox SEC_ERROR_EXPIRED_CERTIFICATE, openssl “certificate has expired”certificate-এর মেয়াদ শেষ — অথবা যন্ত্রের ঘড়ি ভুলনিজের তারিখ আগে দেখুন (CMOS ব্যাটারি, ঘড়ি মেলানো বন্ধ — NTP); তারপর সার্ভারের notAfter
NET::ERR_CERT_COMMON_NAME_INVALID, SSL_ERROR_BAD_CERT_DOMAIN, “hostname mismatch”নাম SAN-এ নেই; wildcard-এর ভুল ধারণা; ভুল সার্ভারে পৌঁছানো (DNS, SNI ছাড়া পুরোনো ক্লায়েন্ট)SAN পড়ুন; ঠিক নামটি দিয়ে সংযোগ করছেন কি না; সার্ভারে SNI অনুযায়ী ঠিক certificate
NET::ERR_CERT_AUTHORITY_INVALID, SEC_ERROR_UNKNOWN_ISSUER, “unable to get local issuer certificate”intermediate পাঠানো হয়নি; self-signed বা নিজের CA; trust store পুরোনো; মাঝের কোনো যন্ত্র interception করছেসার্ভারে full chain ফাইল দিন; পুরোনো যন্ত্রে ca-certificates হালনাগাদ; অপরিচিত issuer দেখলে সতর্ক হোন
“revoked”, ERR_CERT_REVOKEDCA certificate বাতিল করেছে (key ফাঁস, ভুলভাবে দেওয়া)নতুন key দিয়ে নতুন certificate
“handshake failure”, “protocol version”, “no shared cipher”দুই পক্ষে মিল নেই — পুরোনো ক্লায়েন্ট (শুধু TLS 1.0) বা অতি-সীমিত সার্ভার কনফিগারেশনদুই দিকের সংস্করণ আর suite তালিকা দেখুন; ক্লায়েন্ট হালনাগাদ, দুর্বল সংস্করণ চালু করা নয়

সমস্যা খোঁজার চেকলিস্ট (নিজের সার্ভারে)

  1. যন্ত্রের তারিখ-সময় ঠিক আছে কি না — date -u; ঘড়ি মেলানো চালু কি না।
  2. নাম: openssl x509 -noout -ext subjectAltName দিয়ে SAN দেখা; যে নামে সংযোগ, তা আছে কি না।
  3. শিকল: openssl s_client -connect <নিজের-সার্ভার>:443 -servername <নাম> -showcerts — সার্ভার intermediate পাঠাচ্ছে কি না, “Verify return code” কী।
  4. মেয়াদ: openssl x509 -noout -enddate; স্বয়ংক্রিয় নবায়ন (ACME) চলছে কি না।
  5. ঠিক certificate: একাধিক সাইটের সার্ভারে SNI অনুযায়ী ঠিক ফাইল, load balancer আর origin দুই জায়গাতেই হালনাগাদ।
  6. অন্য যন্ত্র থেকে, অন্য নেটওয়ার্ক থেকে পরীক্ষা — শুধু এক নেটওয়ার্কে সমস্যা হলে মাঝের proxy বা interception সন্দেহ করুন।

৭ · Web PKI — CA, revocation, Certificate Transparency আর ACME

ওয়েবের পুরো বিশ্বাস-ব্যবস্থাকে বলে Web PKI: CA-গুলো, ব্রাউজার আর OS-নির্মাতাদের root program (কোন CA trust store-এ থাকবে), আর CA/Browser Forum-এর Baseline Requirements (CA কীভাবে যাচাই করবে)। এর সবচেয়ে বড় দুর্বলতা: trust store-এর যেকোনো CA যেকোনো domain-এর certificate দিতে পারে। একটি CA ভুল করলে বা আক্রান্ত হলে সবাই ঝুঁকিতে। ২০১১ সালে ডাচ CA DigiNotar আক্রান্ত হয়ে google.com-সহ বহু নামের নকল certificate তৈরি হয়েছিল বলে প্রকাশিত হয়; ব্রাউজারগুলো সেটিকে trust store থেকে সরিয়ে দেয় আর প্রতিষ্ঠানটি দেউলিয়া হয়। এমন ঘটনার উত্তরেই নিচের ব্যবস্থাগুলো।

DV, OV, EV — যাচাইয়ের তিন স্তর

DV (Domain Validated): CA শুধু যাচাই করে আবেদনকারী domain-টি নিয়ন্ত্রণ করেন — একটি ফাইল বা DNS record বসিয়ে। OV (Organization Validated): প্রতিষ্ঠানের অস্তিত্বও যাচাই। EV (Extended Validation): আরও কঠোর কাগজপত্র যাচাই। একসময় EV সাইটে ব্রাউজার সবুজ বারে প্রতিষ্ঠানের নাম দেখাত; ব্যবহারকারীরা পার্থক্য খেয়াল করেন না দেখে প্রধান ব্রাউজারগুলো ২০১৯ সালের আশপাশে তা সরিয়ে দেয়। এনক্রিপশনের শক্তি তিনটিতেই এক।

Revocation — বাতিল খবর পৌঁছানো

  • CRL: CA বাতিল certificate-এর serial নম্বরের স্বাক্ষরিত তালিকা প্রকাশ করে। তালিকা বড় হতে পারে, আর নিয়মিত নামাতে হয়।
  • OCSP: ক্লায়েন্ট CA-কে জিজ্ঞেস করে “এই একটি certificate বৈধ?” — কিন্তু এতে CA জেনে যায় আপনি কোন সাইটে যাচ্ছেন, প্রতিটি সংযোগে দেরি বাড়ে, আর OCSP সার্ভার উত্তর না দিলে বেশিরভাগ ব্রাউজার “চলুক” ধরে নেয় (soft-fail) — অর্থাৎ যে আক্রমণকারী পথ নিয়ন্ত্রণ করে, সে উত্তর আটকে দিলেই হলো।
  • OCSP stapling: সার্ভার নিজেই CA-র স্বাক্ষরিত সাম্প্রতিক OCSP উত্তর handshake-এ জুড়ে দেয় — গোপনীয়তা আর দেরি দুই সমস্যাই কমে।
  • ব্রাউজারের নিজস্ব পথ: Chrome গুরুত্বপূর্ণ বাতিল খবর নিজের হালনাগাদে পাঠায় (CRLSets), Firefox সংকুচিত তালিকা (CRLite) চালু করেছে বলে জানা যায় — খুঁটিনাটি ব্রাউজারভেদে বদলায়। বড় CA-দের মধ্যে Let's Encrypt ২০২৪ সালে OCSP ছেড়ে CRL-এ ফেরার ঘোষণা দিয়েছিল।
  • স্বল্পমেয়াদি certificate: সবচেয়ে কার্যকর সমাধান সম্ভবত মেয়াদ ছোট করা — বাতিল খবর না পৌঁছালেও ক্ষতির সময়সীমা ছোট।

Certificate Transparency (CT)

ধারণাটি সহজ: প্রতিটি সর্বজনীনভাবে বিশ্বস্ত certificate কয়েকটি প্রকাশ্য, শুধু-যোগ-করা-যায় এমন log-এ লিখতে হবে (মূল মান RFC 6962, ২০১৩)। Log certificate-টির জন্য একটি স্বাক্ষরিত প্রতিশ্রুতি (SCT) দেয়, যা certificate-এর ভেতরে বা handshake-এ থাকে; Chrome আর Safari নতুন certificate-এ SCT চায়। ফলে কোনো CA গোপনে আপনার domain-এর certificate দিতে পারে না — domain-এর মালিক log পর্যবেক্ষণ করে (crt.sh-এর মতো সেবা দিয়ে) নিজের নামের অপ্রত্যাশিত certificate ধরে ফেলতে পারেন। CT অপব্যবহার ঠেকায় না, কিন্তু দৃশ্যমান করে — আর দৃশ্যমানতাই CA-দের সৎ রাখে। সাথে DNS-এর CAA record দিয়ে আগেই বলে দেওয়া যায় কোন CA আপনার domain-এর certificate দিতে পারবে (পর্ব ৩৫)।

ACME আর Let's Encrypt — certificate হাতে নবায়নের দিন শেষ

২০১৫ সালের শেষে পরীক্ষামূলক আর ২০১৬ সালে পূর্ণভাবে চালু হয় বিনা খরচের CA Let's Encrypt; তার স্বয়ংক্রিয় protocol পরে মান হয় ACME নামে (RFC 8555, ২০১৯)। সার্ভারের একটি ছোট প্রোগ্রাম (যেমন certbot) প্রমাণ দেয় যে domain তার নিয়ন্ত্রণে — http-01 (port 80-এ একটি ফাইল), dns-01 (একটি TXT record, wildcard-এর জন্য দরকার) বা tls-alpn-01 — তারপর certificate নেয় আর নিজেই নবায়ন করে। এরপর ওয়েবে HTTPS-এর অংশ নাটকীয়ভাবে বেড়েছে বলে বিভিন্ন পরিমাপে দেখা যায়।

মেয়াদ ছোট হচ্ছে

একসময় certificate কয়েক বছরের জন্য পাওয়া যেত। ২০২০ সালে সর্বজনীন TLS certificate-এর সর্বোচ্চ মেয়াদ নামে ৩৯৮ দিনে (প্রথমে Apple-এর নীতি, পরে সবাই মেনে নেয়); Let's Encrypt শুরু থেকেই ৯০ দিন দিয়েছে। ২০২৫ সালে CA/Browser Forum ধাপে ধাপে মেয়াদ আরও কমানোর সিদ্ধান্ত নিয়েছে বলে জানা যায় — প্রথম ধাপ (প্রায় ২০০ দিন) ২০২৬ সাল থেকে কার্যকর, আর লক্ষ্য কয়েক বছরের মধ্যে দেড় মাসের কাছাকাছি। নির্দিষ্ট তারিখ আর সীমা Baseline Requirements-এর সর্বশেষ সংস্করণে দেখে নিন। শিক্ষা একটাই: certificate নবায়ন হাতে করার কাজ নয়, স্বয়ংক্রিয় করতে হবে, আর মেয়াদ শেষের সতর্কবার্তা monitoring-এ রাখতে হবে।

mTLS — দুই দিকেই পরিচয়পত্র

সাধারণ HTTPS-এ শুধু সার্ভার প্রমাণ দেয়; ব্যবহারকারী প্রমাণ দেন পরে, password বা OTP দিয়ে। Mutual TLS-এ সার্ভার handshake-এ CertificateRequest পাঠায়, আর ক্লায়েন্টও নিজের certificate ও CertificateVerify পাঠায়। কোথায় দেখবেন: microservice-গুলোর মধ্যে (service mesh প্রতিটি সার্ভিসকে স্বয়ংক্রিয়ভাবে স্বল্পমেয়াদি certificate দেয়), ব্যাংক ও আর্থিক প্রতিষ্ঠানের API, IoT যন্ত্র, অফিসের VPN বা Wi-Fi-তে যন্ত্রের পরিচয়। এখানে সাধারণত প্রতিষ্ঠানের নিজস্ব (private) CA থাকে — Web PKI নয়।

৮ · বাস্তবে HTTPS — HSTS, mixed content, termination আর interception

80 থেকে 443, আর প্রথম অনুরোধের ফাঁক

ব্যবহারকারী ঠিকানাবারে শুধু example.com লিখলে অনেক সময় প্রথম অনুরোধটি যায় http://-তে, port 80-এ, খোলা অবস্থায়। সার্ভার 301 দিয়ে HTTPS-এ পাঠায় (পর্ব ৩৬)। কিন্তু সেই প্রথম খোলা অনুরোধের মুহূর্তে পথের কোনো আক্রমণকারী redirect-টি মুছে দিয়ে ব্যবহারকারীকে HTTP-তেই রেখে দিতে পারে, নিজে সার্ভারের সাথে HTTPS-এ কথা বলে (এই কৌশলকে “SSL stripping” বলা হয়; প্রতিরক্ষা-সহ পর্ব ৫০)। সমাধান HSTS (HTTP Strict Transport Security, RFC 6797, ২০১২): সার্ভার HTTPS উত্তরে বলে দেয় “এই সাইটে আর কখনো HTTP নয়”.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

max-age সেকেন্ডে: ৩১৫৩৬০০০ = ৩৬৫ × ৮৬৪০০, অর্থাৎ এক বছর। includeSubDomains সব sub-domain-এ প্রযোজ্য। শুধু HTTPS উত্তরের header মানা হয় — HTTP উত্তরেরটি নয়, নইলে আক্রমণকারী নিজেই বসিয়ে দিত।

  1. প্রথম ভিজিটhttp://port 80, খোলা
  2. HSTS মনে রাখাmax-age 1 বছরব্রাউজারে
  3. ভেতরেই https307 Internalখোলা কিছু যায় না
  4. নকল certificateএড়ানো যায় নাপৃষ্ঠা বন্ধ
ব্যবহারকারী লেখেন http://www.example.com
মুহূর্ত
নেটওয়ার্কে যা যায়
ব্রাউজার যা করে
ঝুঁকি
  1. প্রথম ভিজিট — খোলা অনুরোধ। ব্রাউজার এই সাইট সম্পর্কে কিছু জানে না, তাই port 80-এ খোলা GET পাঠায়। এই মুহূর্তটিই দুর্বল: পথের আক্রমণকারী উত্তর বদলে ব্যবহারকারীকে HTTP-তে আটকে রাখতে পারে।
  2. Redirect. সার্ভার 301 দিয়ে https ঠিকানায় পাঠায়। এই উত্তরও খোলা — নিজে কোনো সুরক্ষা দেয় না।
  3. HTTPS আর HSTS header. TLS handshake সফল, certificate যাচাই হলো, আর উত্তরে Strict-Transport-Security header এল। যেহেতু এটি এনক্রিপ্টেড পথে এসেছে, ব্রাউজার বিশ্বাস করে।
  4. মনে রাখা। ব্রাউজার নিজের HSTS তালিকায় লেখে: www.example.com আর তার সব sub-domain, মেয়াদ এখন থেকে এক বছর। প্রতিবার নতুন header এলে মেয়াদ নতুন করে গোনা শুরু হয়।
  5. পরের বার — খোলা কিছুই যায় না। ব্যবহারকারী আবার http:// লিখলেন, এবার পাবলিক Wi-Fi-তে। ব্রাউজার নেটওয়ার্কে কিছু পাঠানোর আগেই ঠিকানা https-এ বদলায় — Chrome-এর developer tools-এ এটি “307 Internal Redirect” দেখায়। পথের লোকের হাতে বদলানোর মতো কোনো খোলা অনুরোধ নেই।
  6. নকল certificate — কোনো বিকল্প নেই। কেউ নিজের certificate দিয়ে সংযোগ ধরার চেষ্টা করলে সাধারণ সাইটে ব্রাউজার সতর্কবার্তার সাথে “তবু এগিয়ে যান” দেয়, আর অনেকে চেপে দেন। HSTS সাইটে সেই বোতামই থাকে না।
  7. Preload — প্রথম ভিজিটের ফাঁকও বন্ধ। ধাপ ১-এর দুর্বলতা থেকে যায়। সমাধান: সাইটটিকে ব্রাউজারের ভেতরের HSTS preload তালিকায় জমা দেওয়া (hstspreload.org; শর্ত: দীর্ঘ max-age, includeSubDomains আর preload). তালিকায় থাকলে ব্রাউজার প্রথমবারেই HTTPS ছাড়া যায় না। কিছু পুরো TLD-ও এভাবে তালিকাভুক্ত, যেমন .app আর .dev.

HSTS চালুর আগে ভাবুন। includeSubDomains দিলে কোনো পুরোনো sub-domain (যেমন অফিসের ভেতরের একটি HTTP-only যন্ত্র) আর খুলবে না; preload তালিকা থেকে বের হতে মাসের পর মাস লাগে। ধাপে ধাপে এগোন: ছোট max-age দিয়ে শুরু, সব sub-domain HTTPS-এ আনা, তারপর বাড়ানো।

Mixed content

HTTPS পাতার ভেতরে কোনো script বা ছবি http:// থেকে এলে তাকে বলে mixed content. একটি খোলা script পথে বদলে গেলে পুরো পাতা আক্রমণকারীর হাতে। তাই আধুনিক ব্রাউজার “সক্রিয়” mixed content (script) আটকায়, আর ছবির মতোগুলো https-এ বদলে নেয় বা আটকায় — নীতি ব্রাউজারভেদে ভিন্ন। সমাধান: সব resource HTTPS-এ, চাইলে Content-Security-Policy-তে upgrade-insecure-requests.

TLS termination

বড় সাইটে TLS সাধারণত সামনের load balancer, reverse proxy বা CDN-এর edge-এ খোলা হয় — TLS termination (পর্ব ৩৮; data center-এর load balancer পর্ব ৫২-তে)। সুবিধা: certificate এক জায়গায়, CPU-ভারী কাজ আলাদা যন্ত্রে, HTTP দেখে routing আর cache. মনে রাখুন: edge থেকে origin পর্যন্ত পথও আবার TLS (প্রায়ই mTLS) দিয়ে এনক্রিপ্ট করা উচিত, আর edge-এর হাতে সাইটের key থাকে — সেই প্রতিষ্ঠানকে বিশ্বাস করতে হয়।

ECH — নামটিও লুকানো

TLS 1.3-এ certificate লুকানো গেলেও SNI-তে নাম খোলা। ECH (Encrypted Client Hello) আসল ClientHello এনক্রিপ্ট করে একটি বাইরের ClientHello-র ভেতরে পাঠায়, যার SNI-তে থাকে একটি সাধারণ “public name” (যেমন CDN-এর)। Key আসে DNS-এর HTTPS record থেকে (পর্ব ৩৫, ৩৭)। কিছু ব্রাউজার আর CDN এটি চালু করেছে; মানের বর্তমান অবস্থা IETF-এ যাচাই করে নিন। ECH কাজে লাগে যখন অনেক সাইট একই public name-এর পেছনে থাকে আর DNS-ও এনক্রিপ্টেড থাকে।

TLS interception — অফিসের proxy আর antivirus

কিছু প্রতিষ্ঠানের নিরাপত্তা-যন্ত্র আর কিছু antivirus এনক্রিপ্টেড ট্রাফিক “দেখতে” চায়। কৌশল: কম্পিউটারের trust store-এ নিজেদের একটি root CA বসানো, তারপর মাঝখানে বসে প্রতিটি সাইটের জন্য তাৎক্ষণিক certificate বানানো — কাঠামোগতভাবে একটি অনুমোদিত MITM. ঝুঁকি গবেষণায় নথিভুক্ত: অনেক পণ্য বাইরের certificate ঠিকমতো যাচাই করে না বা দুর্বল TLS চালায়, ফলে ব্যবহারকারী কম সুরক্ষিত হন; সেই root CA-র key ফাঁস হলে সব যন্ত্র ঝুঁকিতে; আর গোপনীয়তার প্রশ্ন। প্রতিরক্ষার দৃষ্টিতে: শুধু লিখিত নীতি আর ব্যবহারকারীকে জানিয়ে, সংবেদনশীল বিভাগ বাদ রেখে, পণ্যের TLS যাচাই নিয়মিত পরীক্ষা করে। নিজের যন্ত্রে certificate-এর issuer-এ অপরিচিত নাম (যেমন কোনো antivirus) দেখলে বুঝবেন কেউ মাঝে আছে।

আরও গভীরে: আজকের ECDHE ভবিষ্যতের বড় quantum কম্পিউটারে ভাঙতে পারে — আর কেউ আজ রেকর্ড করে পরে খুলতে পারে (“harvest now, decrypt later”). তাই Chrome আর Cloudflare-এর মতো বড় প্রতিষ্ঠান ২০২৪ সাল থেকে (পরে অন্যরাও) hybrid key exchange (X25519-এর সাথে ML-KEM, NIST-এর post-quantum মান) চালু করেছে বলে জানা যায়; ফলে ClientHello-র key_share কয়েক ডজন বাইট থেকে এক কিলোবাইটের বেশি হয়েছে — আর কিছু পুরোনো middlebox বড় ClientHello দেখে ভেঙে পড়েছে, ossification-এর নতুন উদাহরণ। এ ছাড়া GREASE (RFC 8701) ইচ্ছা করে অচেনা মান পাঠিয়ে সার্ভারগুলোকে নতুনের জন্য প্রস্তুত রাখে, আর certificate compression (RFC 8879) বড় শিকলের বাইট কমায়।

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • HTTPS-এ handshake-এর ধাপগুলো বলুন। — TCP-র পরে ClientHello (key share, SNI) → ServerHello (key share) → এনক্রিপ্টেড EncryptedExtensions, Certificate, CertificateVerify, Finished → ক্লায়েন্টের Finished, তারপর এনক্রিপ্টেড HTTP.
  • TLS 1.3 কেন TLS 1.2-এর চেয়ে দ্রুত আর নিরাপদ? — প্রথম বার্তায় key share, তাই ১ RTT; RSA key exchange, CBC, compression, renegotiation বাদ; ServerHello-র পর সব এনক্রিপ্টেড; সবসময় forward secrecy.
  • Browser কীভাবে certificate বিশ্বাস করে? — leaf → intermediate → trust store-এর root পর্যন্ত স্বাক্ষর, মেয়াদ, CA:TRUE, SAN-এ নাম, revocation আর CT নীতি।
  • Symmetric আর asymmetric — TLS-এ কোনটি কোথায়? — handshake-এ asymmetric (ECDHE, স্বাক্ষর), ডেটায় symmetric AEAD — কারণ symmetric অনেক দ্রুত।
  • HSTS কী সমস্যা সমাধান করে? — প্রথম খোলা HTTP অনুরোধ আর redirect-এর সময় SSL stripping; preload দিয়ে প্রথম ভিজিটও সুরক্ষিত।

৯ · AI-এর যুগে TLS — কনফিগারেশন, ত্রুটি আর যাচাই

“nginx-এ HTTPS চালু করার config লিখে দিন” — AI মুহূর্তে লিখে দেয়। কিন্তু TLS নিয়ে ইন্টারনেটের পুরোনো লেখা প্রচুর, আর AI প্রায়ই সেগুলো মিশিয়ে ফেলে। চেনা ভুলগুলো:

AI-এর ভুলআসল কথাকীভাবে যাচাই করবেন
পুরোনো cipher তালিকা, TLS 1.0/1.1 চালু রাখা, “SSLv3 for compatibility”শুধু TLS 1.2 আর 1.3; 1.2-এ শুধু ECDHE + AEADMozilla-র SSL Configuration Generator-এর মতো হালনাগাদ উৎসের সাথে মেলান; নিজের সার্ভার পরীক্ষা করুন
ত্রুটি সারাতে verify=False, curl -k, NODE_TLS_REJECT_UNAUTHORIZED=0এটি সারানো নয়, সুরক্ষা বন্ধ করা — MITM-এর দরজা খোলাআসল কারণ খুঁজুন: intermediate, নাম, ঘড়ি, নিজের CA ঠিকভাবে trust store-এ যোগ
CN দিয়ে certificate বানানোর নির্দেশ, SAN ছাড়াআধুনিক ব্রাউজার শুধু SAN দেখেopenssl x509 -noout -ext subjectAltName
ভুল RFC বা সাল (যেমন TLS 1.3-কে RFC 5246 বলা)TLS 1.2 = RFC 5246 (২০০৮), TLS 1.3 = RFC 8446 (২০১৮), বাতিল-ঘোষণা RFC 8996rfc-editor.org-এ নম্বর আর শিরোনাম মেলান
“HSTS preload সবসময় দিন” — পরিণাম না বলেsub-domain ভাঙতে পারে, বের হওয়া ধীরছোট max-age দিয়ে ধাপে ধাপে; সব sub-domain-এর তালিকা আগে বানান
private key আর certificate একসাথে কোথাও পেস্ট করে “যাচাই” করতে বলাprivate key কখনো কাউকে দেখানো যায় না — AI-কেও নয়শুধু certificate (প্রকাশ্য) বা ত্রুটির বার্তা শেয়ার করুন

AI-কে TLS-সংক্রান্ত কাজে নিখুঁত প্রেক্ষাপট দিন আর যাচাইয়ের পথ চেয়ে নিন:

তুমি একজন সতর্ক নিরাপত্তা-প্রকৌশলীর মতো উত্তর দাও। আমার নিজের সার্ভার (nginx 1.24, Ubuntu 24.04) — উদাহরণ নাম www.example.com. ১. শুধু TLS 1.2 আর 1.3 চালু রেখে HTTPS config দাও; প্রতিটি লাইনের মানে লেখো, আর কোন নির্দেশনা কোন সংস্করণ থেকে আছে তা বলো। ২. সার্ভারে full chain পাঠানো হচ্ছে কি না, আর মেয়াদ কবে শেষ — openssl দিয়ে যাচাইয়ের command দাও, আউটপুট কীভাবে পড়ব তাও। ৩. HSTS ধাপে ধাপে চালুর পরিকল্পনা দাও (max-age কত দিয়ে শুরু), includeSubDomains আর preload-এর ঝুঁকি আলাদা করে লেখো। ৪. কোথাও verify বন্ধ করা বা পুরোনো cipher চালু করার পরামর্শ দেবে না; private key আমাকে কোথাও পেস্ট করতে বলবে না। ৫. প্রতিটি দাবির পাশে RFC বা অফিসিয়াল ডকুমেন্টেশনের নাম দাও; নিশ্চিত না হলে স্পষ্ট বলো।

AI-এর TLS-পরামর্শ চালু করার আগে — চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১TLS 1.3-এ legacy_version কেন 0x0303, আর অর্থহীন ChangeCipherSpec কেন পাঠানো হয়? নতুন সংস্করণ চালু করতে এত কৌশল লাগল কেন?

TLS-এর নকশায় সংস্করণ নির্বাচনের একটি সরল নিয়ম ছিল: ক্লায়েন্ট ClientHello-তে নিজের সর্বোচ্চ সংস্করণ লেখে, সার্ভার নিজের সমর্থনের মধ্যে সবচেয়ে উঁচুটি বেছে নেয়। বাস্তবে বহু সার্ভার এই নিয়ম ভুলভাবে বাস্তবায়ন করেছিল — অচেনা, উঁচু সংস্করণ দেখলে নিচেরটি বেছে না নিয়ে সংযোগই ফেলে দিত (“version intolerance”). ফলে ব্রাউজারগুলো বছরের পর বছর একটি বিপজ্জনক কৌশল চালাত: সংযোগ ব্যর্থ হলে নিচের সংস্করণ দিয়ে আবার চেষ্টা। POODLE (২০১৪) ঠিক এই fallback-কে কাজে লাগিয়েছিল — আক্রমণকারী সংযোগ ভেঙে দিলে ব্রাউজার নিজেই SSL 3.0-এ নেমে আসত।

TLS 1.3-এর খসড়া যখন ২০১৬–১৭ সালে বড় পরিসরে পরীক্ষা হলো, আরও বড় সমস্যা ধরা পড়ল: পথের অনেক middlebox — firewall, IDS, “TLS-ত্বরণকারী” — নিজেরা TLS 1.2-এর handshake দেখে অভ্যস্ত। ServerHello-র পর ChangeCipherSpec না দেখলে, বা অচেনা version দেখলে, তারা সংযোগ কেটে দিত। Chrome আর Firefox-এর পরিমাপে দেখা গিয়েছিল একটি উল্লেখযোগ্য অংশ সংযোগ এভাবে ব্যর্থ হচ্ছে — নতুন সংস্করণ চালু করা কার্যত অসম্ভব হয়ে পড়ছিল।

সমাধান ছিল ছদ্মবেশ। TLS 1.3 নিজেকে বাইরে থেকে TLS 1.2-এর resumption-এর মতো দেখায়: legacy_version 0x0303, আসল সংস্করণ লুকানো supported_versions extension-এ; ৩২ বাইটের একটি session id; আর একটি অর্থহীন ChangeCipherSpec record — এই runner-এর openssl আউটপুটে আপনি দুই দিক থেকেই সেটি দেখেছেন। এর সাথে downgrade ঠেকাতে সার্ভারের random-এর শেষ ৮ বাইটে বিশেষ চিহ্ন বসানো হয়, যদি সে TLS 1.3 পারে কিন্তু নিচেরটি বেছে নিতে বাধ্য হয়; ক্লায়েন্ট তা দেখে বুঝে যায় কেউ মাঝে কারসাজি করছে। আর ভবিষ্যতের জন্য GREASE: ক্লায়েন্ট নিয়মিত এলোমেলো, অর্থহীন version আর extension নম্বর পাঠায়, যাতে সার্ভার আর middlebox “অচেনা মান উপেক্ষা করতে হবে” নিয়মটি মানতে বাধ্য থাকে।

এই অভিজ্ঞতাই QUIC-কে প্রায় পুরো header এনক্রিপ্ট করতে শিখিয়েছে (পর্ব ৩৩): যা দেখা যায় না, তার ওপর middlebox নির্ভর করতে পারে না।

মূল উপলব্ধি: একটি protocol-এর যে অংশ পথ থেকে দেখা যায়, সেটিই একসময় জমে যায় (ossification). নিরাপদ নকশা মানে শুধু শক্ত গণিত নয় — বিবর্তনের পথ খোলা রাখাও।

প্র ০২Heartbleed (২০১৪) protocol-এর দোষ ছিল না, তবু ইন্টারনেট কেঁপে উঠেছিল কেন? এর থেকে সফটওয়্যার-জগৎ কী শিখেছে?

TLS-এর একটি ছোট extension ছিল “heartbeat”: এক পক্ষ কিছু বাইট পাঠিয়ে বলে “এগুলো ফেরত পাঠান”, সংযোগ সজীব কি না দেখতে। বার্তায় থাকত ডেটা আর তার দৈর্ঘ্য। OpenSSL-এর কোড দাবি করা দৈর্ঘ্য বিশ্বাস করত, আসল দৈর্ঘ্যের সাথে মেলাত না। কেউ ১ বাইট পাঠিয়ে বললে “৬৪ কিলোবাইট ফেরত পাঠান”, সার্ভার নিজের memory থেকে পাশের ৬৪ কিলোবাইট পাঠিয়ে দিত — তাতে থাকতে পারত অন্য ব্যবহারকারীর cookie, password, এমনকি সার্ভারের private key. আর এই পড়া কোনো লগে চিহ্ন রাখত না।

২০১৪ সালের এপ্রিলে প্রকাশের সময় দুর্বলতাটি বছর দুয়েক ধরে প্রকাশিত OpenSSL সংস্করণে ছিল, আর OpenSSL ছিল ইন্টারনেটের বিপুল সংখ্যক সার্ভার, router আর যন্ত্রের TLS. প্রতিক্রিয়ার কাজ ছিল বিশাল: patch, তারপর ধরে নেওয়া যে private key ফাঁস হয়ে থাকতে পারে — তাই নতুন key, নতুন certificate, পুরোনোগুলো revoke, ব্যবহারকারীদের password বদলাতে বলা। Revocation-এর দুর্বলতা (অংশ ৭) তখন খুব স্পষ্ট হয়ে ওঠে: লাখো certificate একসাথে বাতিল হলে CRL ফুলে ওঠে, আর soft-fail OCSP তেমন রক্ষা দেয় না।

শিক্ষাগুলো দীর্ঘমেয়াদি। প্রথমত, ইন্টারনেটের ভিত্তি-সফটওয়্যার চলছিল খুব অল্প, প্রায় স্বেচ্ছাসেবী রক্ষণাবেক্ষণকারীর ওপর; এরপর বড় প্রতিষ্ঠানগুলো মিলে এমন প্রকল্পে অর্থায়ন শুরু করে (Linux Foundation-এর Core Infrastructure Initiative, পরে OpenSSF). দ্বিতীয়ত, OpenSSL-এর বিকল্প ও শাখা জন্ম নেয় — OpenBSD-র LibreSSL, Google-এর BoringSSL — আর পরে memory-নিরাপদ ভাষায় লেখা rustls-এর মতো লাইব্রেরি। তৃতীয়ত, “ঐচ্ছিক, কম-ব্যবহৃত feature” মানেও আক্রমণের জায়গা; TLS 1.3 এই দর্শনেই বহু বিকল্প বাদ দিয়েছে। চতুর্থত, forward secrecy-র মূল্য বোঝা গেল: ECDHE ব্যবহারকারী সার্ভারের key চুরি হলেও আগের রেকর্ড করা আলাপ খোলা যেত না।

মূল উপলব্ধি: নিরাপত্তা একটি শিকলের মতো — protocol, বাস্তবায়ন, key ব্যবস্থাপনা আর revocation; সবচেয়ে দুর্বল কড়াটিই পুরো শক্তি ঠিক করে।

প্র ০৩Trust store-এর যেকোনো CA যেকোনো domain-এর certificate দিতে পারে — এই নকশা কেন টিকে আছে, আর এর ঝুঁকি কীভাবে সামলানো হচ্ছে?

Web PKI-র মূল সমস্যা গণিতের নয়, প্রতিষ্ঠানের। আপনার ব্রাউজার শতাধিক root-কে সমানভাবে বিশ্বাস করে, তারা বিভিন্ন দেশের, বিভিন্ন আইনের অধীন। এদের যেকোনো একটির ভুল, অবহেলা বা চাপের মুখে নতিস্বীকার মানে যেকোনো সাইটের নকল certificate — আর তা দিয়ে নিখুঁত MITM. এর বিকল্প কী হতে পারত? প্রতিটি সাইটের key নিজে হাতে মেলানো (SSH-এর মতো “প্রথমবার বিশ্বাস”) কোটি মানুষের পক্ষে অসম্ভব; একটিমাত্র বিশ্ব-কর্তৃপক্ষ রাজনৈতিকভাবে অগ্রহণযোগ্য। তাই বহু CA-র প্রতিযোগিতামূলক ব্যবস্থা টিকে আছে — আর তার ঝুঁকি সামলাতে স্তরে স্তরে পাহারা বসেছে।

ইতিহাস এই পাহারার কারণ দেখায়। ২০১১ সালে DigiNotar আক্রান্ত হয়ে google.com-এর নকল certificate তৈরি হয়, যা একটি দেশে বহু ব্যবহারকারীর ওপর নজরদারিতে ব্যবহৃত হয়েছিল বলে প্রকাশিত হয়; ধরা পড়ে মূলত Chrome-এর একটি pinning ব্যবস্থার কারণে। একই বছর Comodo-র একটি reseller-এর মাধ্যমেও কিছু ভুল certificate দেওয়া হয়। পরে বছরের পর বছর অনিয়মের প্রমাণে ব্রাউজারগুলো ২০১৭–১৮ সালে Symantec-এর পুরোনো root-গুলোর ওপর বিশ্বাস ধাপে ধাপে তুলে নেয় — বিশ্বের সবচেয়ে বড় CA-গুলোর একটি হয়েও।

আজকের পাহারা কয়েক স্তরের। Certificate Transparency সব certificate প্রকাশ্য log-এ আনে, তাই ভুল certificate গোপন থাকে না; domain-এর মালিক নিজে পর্যবেক্ষণ করতে পারেন। CAA record দিয়ে domain বলে দেয় কোন CA তার certificate দিতে পারবে, আর CA-দের তা মানা বাধ্যতামূলক। Root program-গুলো (Mozilla, Apple, Microsoft, Google) নিয়মিত নিরীক্ষা চায়, ঘটনা প্রকাশ্যে আলোচনা করে, আর নিয়ম ভাঙলে CA-কে সরিয়ে দেয়। স্বল্প মেয়াদ ভুল certificate-এর আয়ু কমায়। Name constraints দিয়ে কোনো intermediate-কে শুধু নির্দিষ্ট domain-এ সীমিত করা যায়। আর বিশেষ ক্ষেত্রে অ্যাপ নিজে নির্দিষ্ট key “pin” করতে পারে — যদিও ভুল pin সাইট অচল করে দিতে পারে বলে ওয়েবে HTTP-ভিত্তিক pinning (HPKP) পরিত্যক্ত হয়েছে।

মূল উপলব্ধি: বিশ্বাস-ব্যবস্থা নিখুঁত হয় না; তাকে নিরাপদ রাখে স্বচ্ছতা, জবাবদিহি আর দ্রুত শাস্তি — CT তাই Web PKI-র সবচেয়ে গুরুত্বপূর্ণ উদ্ভাবনগুলোর একটি।

প্র ০৪Revocation-কে প্রায়ই “ভাঙা” বলা হয়। কেন? আর ছোট মেয়াদের certificate কি সত্যিই এর সমাধান?

একটি certificate-এর private key চুরি হলো। মালিক CA-কে জানালেন, CA certificate-টি বাতিল ঘোষণা করল। কিন্তু সেই ঘোষণা বিশ্বের কোটি ব্রাউজারে পৌঁছাবে কীভাবে, আর চোর যখন নিজেই আক্রান্ত ব্যবহারকারীর নেটওয়ার্ক-পথে বসে আছে তখন? এটাই revocation-এর মৌলিক সমস্যা।

CRL ছিল প্রথম উত্তর: বাতিল serial নম্বরের তালিকা। কিন্তু বড় CA-র তালিকা মেগাবাইট ছাড়াতে পারে, আর প্রতিটি সংযোগের আগে নামানো অবাস্তব। OCSP প্রতিটি certificate-এর জন্য আলাদা প্রশ্ন করে — হালকা, কিন্তু তিনটি গভীর সমস্যা নিয়ে: (১) CA জেনে যায় কে কোন সাইটে যাচ্ছে; (২) প্রতিটি নতুন সংযোগে বাড়তি একটি অনুরোধ, আর OCSP সার্ভার ধীর হলে পাতাও ধীর; (৩) উত্তর না এলে কী করবে? “সংযোগ বন্ধ” (hard-fail) নিলে OCSP সার্ভার বিকল হলে অর্ধেক ওয়েব অচল; তাই প্রায় সব ব্রাউজার “চলুক” (soft-fail) নিয়েছে। কিন্তু যে আক্রমণকারী চুরি করা key নিয়ে MITM করছে, সে OCSP অনুরোধও আটকে দিতে পারে — ঠিক যখন revocation দরকার, তখনই সেটি নিষ্ক্রিয়।

OCSP stapling প্রথম দুটি সমস্যা সমাধান করে — সার্ভার নিজেই সাম্প্রতিক স্বাক্ষরিত উত্তর আনে। তৃতীয়টির জন্য “Must-Staple” extension প্রস্তাব করা হয়েছিল, কিন্তু ব্যবহার সীমিত থেকেছে। তাই ব্রাউজারগুলো নিজেদের পথ নিল: Chrome-এর CRLSets গুরুত্বপূর্ণ বাতিলগুলো হালনাগাদে পাঠায়; Firefox-এর CRLite পুরো তালিকা সংকুচিত ডেটা-কাঠামোয় (Bloom filter-এর ধারায়) সবার কাছে পৌঁছানোর চেষ্টা করেছে।

ছোট মেয়াদ এই সবের পরিপূরক। ৯০ দিন বা তার কম মেয়াদের certificate-এ revocation ব্যর্থ হলেও চুরি হওয়া key-এর ক্ষতির জানালা ছোট; কয়েক দিনের মেয়াদ হলে revocation প্রায় অপ্রয়োজনীয়। কিন্তু দাম আছে: স্বয়ংক্রিয় নবায়ন না থাকলে ঘন ঘন বিভ্রাট, CT log-এ বিপুল চাপ, আর CA-র ওপর নির্ভরতা বাড়ে — CA বা ACME ব্যবস্থা কিছুদিন বন্ধ থাকলে নবায়ন আটকে যাবে। তাই ছোট মেয়াদ সমাধানের দিকে বড় পদক্ষেপ, তবে তার পূর্বশর্ত সম্পূর্ণ স্বয়ংক্রিয় পরিচালনা।

মূল উপলব্ধি: একটি বিতরণ-ব্যবস্থায় “বাতিল” খবর ছড়ানো “বৈধ” খবরের চেয়ে কঠিন। তাই প্রকৌশলীরা প্রশ্নটিই বদলেছেন — বাতিল করার বদলে মেয়াদই এত ছোট রাখা যাতে বাতিলের দরকার কম পড়ে।

প্র ০৫একটি প্রতিষ্ঠান নিরাপত্তার জন্য সব HTTPS ট্রাফিক “খুলে দেখতে” চায়, অন্যদিকে ECH আর এনক্রিপ্টেড DNS নামটুকুও লুকাচ্ছে। এই টানাপোড়েনে প্রকৌশলী হিসেবে আপনার অবস্থান কী হবে?

দুই পক্ষেরই যুক্তি বাস্তব। প্রতিষ্ঠানের নিরাপত্তা-দল বলে: malware আজ প্রায় সবসময় HTTPS দিয়েই নির্দেশ নেয় আর ডেটা পাচার করে; গোপন তথ্য ভুল জায়গায় যাচ্ছে কি না দেখার দায়ও তাদের; কিছু খাতে নিয়ন্ত্রক সংস্থা নজরদারি চায়। এনক্রিপশনের যুগে তারা চোখ হারিয়েছে, তাই TLS interception বা অন্তত SNI দেখে নাম-ভিত্তিক filtering. অন্য পক্ষ বলে: ব্যবহারকারীর গোপনীয়তা, আর প্রমাণ — ২০১৭ সালের একটি পরিচিত গবেষণাসহ কয়েকটি বিশ্লেষণে দেখা গেছে অনেক interception পণ্য ও antivirus বাইরের certificate ঠিকমতো যাচাই করে না বা দুর্বল TLS ব্যবহার করে, ফলে সুরক্ষার নামে সুরক্ষা কমে। আর ECH-এর মতো প্রযুক্তি সাংবাদিক, অধিকারকর্মী আর সাধারণ মানুষকে পথের নজরদারি থেকে রক্ষা করে।

একজন প্রকৌশলীর ভারসাম্যপূর্ণ অবস্থান কয়েকটি নীতিতে দাঁড়াতে পারে। প্রথমত, প্রান্তে দেখা, পথে নয়: প্রতিষ্ঠানের নিজের যন্ত্রে endpoint সুরক্ষা (EDR) আর ব্রাউজার-নীতি দিয়ে যা দরকার তা দেখা যায়, TLS ভাঙা ছাড়াই; এতে এনক্রিপশনের সুরক্ষা অক্ষত থাকে। দ্বিতীয়ত, interception যদি একান্ত দরকার হয়, তবে লিখিত নীতি, আইনি ভিত্তি আর স্বচ্ছতা — কর্মীরা জানবেন কী দেখা হয়; ব্যাংকিং, স্বাস্থ্য আর ব্যক্তিগত বিভাগ বাদ; interception যন্ত্র নিজে নিয়মিত পরীক্ষা করা যে এটি বাইরের certificate কঠোরভাবে যাচাই করে আর শুধু TLS 1.2/1.3 চালায়; root CA-র private key কঠোর পাহারায়। তৃতীয়ত, প্রতিষ্ঠানের যন্ত্র আর ব্যক্তিগত যন্ত্র আলাদা — কর্মীর নিজের ফোনে প্রতিষ্ঠানের root CA বসানো চাওয়া গোপনীয়তার বড় লঙ্ঘন। চতুর্থত, নাম-ভিত্তিক filtering-এর জন্য প্রতিষ্ঠানের নিজের DNS resolver ব্যবহার, যা প্রতিষ্ঠানের যন্ত্রে নীতি দিয়ে বাধ্যতামূলক করা যায়।

লক্ষ করুন, প্রযুক্তির গতি একদিকেই: TLS 1.3 certificate লুকিয়েছে, QUIC header লুকিয়েছে, ECH নাম লুকাচ্ছে। “পথে বসে দেখা” মডেল দিন দিন কঠিন হবে, আর নিরাপত্তার দায় সরে যাচ্ছে প্রান্তে — zero trust নকশার মূল কথাও এটি (পর্ব ৪৬)।

মূল উপলব্ধি: এনক্রিপশন ভেঙে নিরাপত্তা আনলে প্রায়ই নতুন দুর্বলতা জন্মায়। দীর্ঘমেয়াদি পথ হলো এনক্রিপশন অক্ষত রেখে প্রান্তে, স্বচ্ছ নীতিতে সুরক্ষা।

অনুশীলন

খাতা-কলম নিন — RTT, বাইট আর certificate-এর ঘর ধরে ধরে হিসাব করুন।

  1. হিসাব (RTT আর record): (ক) RTT ৮০ মিলিসেকেন্ড। নতুন সংযোগে প্রথম বাইট পর্যন্ত সময় বের করুন: TCP + TLS 1.2, TCP + TLS 1.3, আর TCP + TLS 1.3 resumption-সহ 0-RTT. (খ) AES-GCM-এ TLS 1.3 দিয়ে ২ MiB (২০,৯৭,১৫২ বাইট) ফাইল পূর্ণ আকারের record-এ পাঠানো হলো। কয়টি record, আর record-স্তরের বাড়তি বাইট কত?

    (ক) সূত্র: (handshake-এর RTT + ১) × RTT, যেখানে শেষ ১ হলো অনুরোধ-উত্তর। TCP + TLS 1.2: (১ + ২ + ১) × ৮০ = ৩২০ ms. TCP + TLS 1.3: (১ + ১ + ১) × ৮০ = ২৪০ ms. 0-RTT-এ অনুরোধ যায় ClientHello-র সাথেই, কিন্তু TCP-র handshake তো লাগবেই: (১ + ০ + ১) × ৮০ = ১৬০ ms. প্রক্রিয়াকরণ, DNS আর transmission-এর সময় বাদ।

    (খ) record-প্রতি সর্বোচ্চ ১৬,৩৮৪ বাইট: ২০৯৭১৫২ ÷ ১৬৩৮৪ = ঠিক ১২৮টি record. প্রতিটিতে বাড়তি ৫ + ১ + ১৬ = ২২ বাইট, মোট ১২৮ × ২২ = ২,৮১৬ বাইট — ফাইলের প্রায় ০.১৩%. (TCP/IP header আলাদা, আর বাস্তবে অনেক বাস্তবায়ন শুরুতে ছোট record পাঠায় যাতে প্রথম বাইট দ্রুত পৌঁছায়।)

  2. হাতে trace (নাম মেলানো): একটি certificate-এর SAN: example.com, *.example.com, *.shop.example.com. আজকের তারিখ মেয়াদের মধ্যে, শিকল ঠিক। নিচের কোন নামে সংযোগ বৈধ? (১) example.com (২) www.example.com (৩) a.shop.example.com (৪) a.b.shop.example.com (৫) shop.example.com (৬) example.org

    (১) বৈধ — সরাসরি এন্ট্রি। (২) বৈধ — *.example.com, এক label (www). (৩) বৈধ — *.shop.example.com, এক label (a). (৪) অবৈধ — *.shop.example.com-এর জন্য দুই label (a.b), আর *.example.com-এর জন্য তিন label; wildcard ঠিক একটি label ঢাকে। (৫) বৈধ — *.example.com-এর এক label “shop”. (৬) অবৈধ — ভিন্ন domain. লক্ষ করুন (১)-এর জন্য আলাদা এন্ট্রি না থাকলে সেটিও অবৈধ হতো — *.example.com খোদ example.com ঢাকে না।

  3. আউটপুট অনুমান করুন: অংশ ৬-এর chain validator-এ cases তালিকা বদলে শুধু এই দুটি রাখা হলো: ["apex via wildcard", "example.com", [leaf({ san: ["*.example.com"] }), inter()]] আর ["old intermediate", "www.example.com", [leaf(), inter({ to: "2026-06-30" })]]. কী ছাপবে?
    ## apex via wildcard
       FAIL example.com matches SAN [*.example.com]
    ## old intermediate
       ok   www.example.com matches SAN [www.example.com, example.com]
       ok   "www.example.com" valid 2026-09-01 .. 2026-11-29
       ok   issuer "Demo CA 1" found in the chain the server sent
       FAIL "Demo CA 1" valid 2024-01-01 .. 2026-06-30

    প্রথমটিতে nameMatches("example.com", "*.example.com"): প্রথম বিন্দুর পরের অংশ “com”, যা “example.com” নয় — wildcard শূন্য label ঢাকে না, তাই প্রথম পরীক্ষাতেই থামে। দ্বিতীয়টিতে leaf ঠিক, কিন্তু intermediate-এর মেয়াদ জুনে শেষ; validator শিকল ধরে ওপরে উঠে সেখানে থামে। বাস্তবে এমন হয় যখন সার্ভারে পুরোনো chain ফাইল রয়ে যায় — leaf নতুন, intermediate পুরোনো। আমরা আসলে চালিয়ে মিলিয়ে দেখেছি।

  4. Command লিখুন (নিজের সার্ভারের জন্য): (ক) নিজের একটি সার্ভার www.example.com-এর (উদাহরণ নাম) পাঠানো শিকল, যাচাইয়ের ফল আর leaf-এর মেয়াদ দেখার openssl command লিখুন। (খ) একটি PEM ফাইলের certificate আগামী ৩০ দিনের মধ্যে শেষ হবে কি না — script-এ ব্যবহারযোগ্য একটি command. (গ) nginx-এ HSTS header-এর একটি নিরাপদ শুরুর লাইন।
    # (ক) শিকল আর যাচাই — শেষে "Verify return code: 0 (ok)" খুঁজুন
    openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts </dev/null
    # leaf-এর মেয়াদ আর নাম
    openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null \
      | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
    
    # (খ) ৩০ দিন = 2592000 সেকেন্ড; exit code 0 = মেয়াদ থাকবে, 1 = শেষ হয়ে যাবে
    openssl x509 -in cert.pem -noout -checkend 2592000
    
    # (গ) শুরুতে ছোট max-age (১ দিন), সব ঠিক থাকলে ধাপে ধাপে বাড়ানো
    add_header Strict-Transport-Security "max-age=86400" always;

    -servername না দিলে SNI যায় না, আর একাধিক সাইটের সার্ভার ভুল (default) certificate দিতে পারে — বিভ্রান্তিকর “name mismatch”-এর সাধারণ কারণ। -showcerts সার্ভারের পাঠানো সব certificate দেখায়; শুধু একটি দেখলে intermediate পাঠানো হচ্ছে না। -checkend-এর exit code monitoring-এ সতর্কবার্তার জন্য সুবিধাজনক। nginx-এ always দিলে ত্রুটির উত্তরেও header যায়। এই command শুধু নিজের বা অনুমতিপ্রাপ্ত সার্ভারে।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি নতুন TLS 1.3 সংযোগের handshake বার্তাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ক্লায়েন্ট ClientHello পাঠায় — key_share, SNI আর ALPN-সহ
  2. সার্ভার ServerHello পাঠায় — বেছে নেওয়া suite আর নিজের key_share
  3. সার্ভার এনক্রিপ্টেড EncryptedExtensions পাঠায়
  4. সার্ভার Certificate পাঠায় — leaf আর intermediate
  5. সার্ভার CertificateVerify পাঠায় — transcript-এর ওপর স্বাক্ষর
  6. সার্ভার Finished, তারপর ক্লায়েন্ট Finished আর প্রথম অনুরোধ

TLS 1.3 handshake-এ সার্ভার কোন বার্তা দিয়ে প্রমাণ করে যে certificate-এর private key সত্যিই তার কাছে আছে?

  • Certificate
  • ServerHello
  • CertificateVerify
  • Finished

একটি certificate-এর SAN-এ শুধু *.example.com আছে। কোন নামে সংযোগ করলে hostname যাচাই সফল হবে?

  • example.com
  • mail.example.com
  • a.b.example.com
  • www.example.com.bd

ECH ছাড়া একটি TLS 1.3 সংযোগে একই Wi-Fi-তে বসা একজন পর্যবেক্ষক কোনটি দেখতে পায়?

  • সার্ভারের certificate-এর বিষয়বস্তু
  • অনুরোধের URL path আর cookie
  • সার্ভারের বেছে নেওয়া ALPN মান
  • ClientHello-র SNI-তে সার্ভারের নাম

একটি API সার্ভার ব্রাউজারে ঠিকমতো খোলে, কিন্তু একটি Linux সার্ভার থেকে curl দিলে “unable to get local issuer certificate” আসে। সবচেয়ে সম্ভাব্য কারণ কী?

  • সার্ভার intermediate certificate পাঠাচ্ছে না
  • certificate-এর মেয়াদ শেষ
  • SAN-এ নাম নেই
  • curl TLS 1.3 সমর্থন করে না

একটি সাইট HTTPS উত্তরে Strict-Transport-Security: max-age=31536000 পাঠায়। পরের মাসে একজন ব্যবহারকারী ঠিকানাবারে http:// দিয়ে সাইটটি লিখলেন। কী ঘটে?

  • ব্রাউজার port 80-এ অনুরোধ পাঠায়, সার্ভার 301 দিয়ে HTTPS-এ পাঠায়
  • ব্রাউজার ব্যবহারকারীকে জিজ্ঞেস করে HTTP না HTTPS
  • ব্রাউজার নিজেই ঠিকানা https-এ বদলে নেয়, খোলা কোনো অনুরোধ যায় না
  • পাতা খোলে না, কারণ http নিষিদ্ধ

TLS 1.2-এর cipher suite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256-এ “RSA” অংশটির কাজ কী?

  • সব ডেটা এনক্রিপ্ট করা
  • সার্ভারের পরিচয়-প্রমাণ — certificate-এর RSA key দিয়ে স্বাক্ষর
  • গোপন session key বিনিময়
  • handshake-এর hash ফাংশন

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. TLS দেয় গোপনীয়তা, অখণ্ডতা আর সার্ভারের পরিচয় (ঐচ্ছিক ক্লায়েন্টেরও — mTLS); দেয় না IP, আকার, সময় আর (ECH ছাড়া) SNI লুকানো, আর প্রান্তের নিরাপত্তা।
  2. TLS 1.3 (RFC 8446, ২০১৮): ClientHello-তে key share → ১ RTT; ServerHello-র পর সব এনক্রিপ্টেড; CertificateVerify = key-এর মালিকানার প্রমাণ; Finished = transcript-এর HMAC; HKDF key schedule; সবসময় forward secrecy. TLS 1.0/1.1 বাতিল (RFC 8996).
  3. Certificate: X.509-এ subject, issuer, মেয়াদ, public key, SAN (নাম শুধু এখানে), key usage, basic constraints. Wildcard ঠিক এক label.
  4. শিকল: leaf → intermediate → trust store-এর root; সার্ভার intermediate পাঠাবে, root নয়। যাচাই: পথ, স্বাক্ষর, মেয়াদ, CA:TRUE, নাম, তারপর revocation আর CT. ত্রুটিতে আগে দেখুন ঘড়ি, নাম, intermediate.
  5. বাস্তবে: ACME দিয়ে স্বয়ংক্রিয় নবায়ন (মেয়াদ ছোট হচ্ছে), HSTS আর preload, mixed content নয়, termination-এর পরেও ভেতরের পথ এনক্রিপ্ট, interception শুধু স্বচ্ছ নীতিতে।

পরের পর্বে: TLS একটি অ্যাপের সংযোগ রক্ষা করে; কিন্তু পুরো নেটওয়ার্কের দরজায় পাহারা আর দুই অফিসের মাঝে নিরাপদ সুড়ঙ্গ? Firewall (nftables), IPsec আর WireGuard — কোন ট্রাফিক ঢুকবে, আর VPN কীভাবে পুরো IP packet-কেই খামে ভরে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান