Kendi Sertifika Otoritesini oluştur, sunucu ve istemci sertifikaları üret, karşılıklı TLS bağlantısı kur. Her komut, her parametre, her adım açıklamalı.
Her şey izole bir klasörde olsun. Bitince silersin, makinen kirlenmez.
mkdir mtls-demo
cd mtls-demo
CA'nın iki şeye ihtiyacı var: kendi gizli anahtarı ve kendi sertifikası. CA'nın sertifikası self-signed olacak (kendi kendini imzalayacak) çünkü en tepedeki otorite — üstünde imzalayacak kimse yok.
openssl genrsa -out ca.key 2048
2048 bitlik RSA anahtar çifti üretir, gizli anahtarı ca.key dosyasına yazar. Açık anahtar gizli anahtarın içinden matematiksel olarak türetilebildiği için ayrı bir dosyaya yazılmaz.
ca.key ultra gizlidir. Bu dosya çalınırsa tüm sistem çöker — saldırgan istediği sertifikayı üretebilir. Gerçek bir CA'da bu dosya internetten kopuk bir makinede, hatta özel donanımda (HSM) tutulur.
openssl req -new -x509 -key ca.key -out ca.crt -days 3650 \
-subj "/C=TR/ST=Istanbul/O=Benim CA'm/CN=Benim Root CA"
Bu komut sonunda ca.crt'nin içinde: CA'nın açık anahtarı + CA bilgileri + CA'nın kendi gizli anahtarıyla yaptığı imza var. Self-signed olduğu için Issuer ve Subject aynı.
openssl x509 -in ca.crt -noout -text
Çıktıda Issuer ve Subject alanlarının aynı olduğunu göreceksin — bu self-signed olmanın tanımı.
Üç adım: gizli anahtarı üret, CSR oluştur, CA'ya imzalat. Bu, gerçek dünyada bir web sitesi için sertifika almanın birebir aynısı — sadece CA olarak Let's Encrypt yerine kendi CA'mızı kullanıyoruz.
openssl genrsa -out server.key 2048
Bu anahtar sunucuda kalacak, hiçbir yere gitmeyecek. CA bile bunu görmeyecek.
openssl req -new -key server.key -out server.csr \
-subj "/C=TR/ST=Istanbul/O=Benim Sirketim/CN=localhost"
Burada -x509 yok — bu sefer CSR üretiyoruz, sertifika değil. CSR'ın içinde: sunucu bilgileri + sunucunun açık anahtarı + sunucunun kendi gizli anahtarıyla yaptığı imza. CN=localhost yazdık çünkü yerel makinada test edeceğiz.
Modern istemciler SAN (Subject Alternative Name) olmadan sertifikayı kabul etmez. Bir config dosyası lazım:
cat > server_ext.cnf << 'EOF'
subjectAltName = DNS:localhost, IP:127.0.0.1
extendedKeyUsage = serverAuth
EOF
openssl x509 -req -in server.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 365 \
-extfile server_ext.cnf
CSR'daki imza önce doğrulanır (CSR'daki açık anahtarla) — "bu CSR gerçekten gizli anahtar sahibinden gelmiş mi". Sonra yeni sertifika oluşturulur: CSR'dan gelen bilgiler + eklenen uzantılar + CA'nın gizli anahtarıyla yeni imza.
Süreç tamamen aynı, tek fark extendedKeyUsage = clientAuth olması. Normal TLS'te bu adım yok — mTLS'i mTLS yapan şey bu.
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr \
-subj "/C=TR/ST=Istanbul/O=Benim Sirketim/CN=ali-musteri"
CN=ali-musteri — istemcinin kim olduğunu belirten bir isim. Gerçek hayatta bu bir kullanıcı adı, servis adı veya cihaz ID'si olabilir.
cat > client_ext.cnf << 'EOF'
extendedKeyUsage = clientAuth
EOF
İstemci sertifikasında SAN gerekmiyor çünkü istemci bir host değil — ona bağlanan olmayacak. Sadece clientAuth damgası yeterli.
openssl x509 -req -in client.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 365 \
-extfile client_ext.cnf
Şu an elimizde 8 dosya var. Hangisi gizli, hangisi paylaşılabilir, hangisi nerede durmalı?
| Dosya | Ne | Nerede Durmalı |
|---|---|---|
| ca.key | CA gizli anahtarı | ULTRA GİZLİ — sadece CA'da |
| ca.crt | CA sertifikası | Herkese verilir (güven kökü) |
| server.key | Sunucu gizli anahtarı | Sunucuda, hiç çıkmaz |
| server.csr | Sunucu CSR'ı | İşi bitti, silinebilir |
| server.crt | Sunucu sertifikası | Sunucuya konulur, herkese gösterilir |
| client.key | İstemci gizli anahtarı | İstemcide, hiç çıkmaz |
| client.csr | İstemci CSR'ı | İşi bitti, silinebilir |
| client.crt | İstemci sertifikası | İstemciye konulur, sunucuya gösterilir |
Hatırlatma: .key uzantılı hiçbir dosya ağa gitmez, başka makineye kopyalanmaz, e-postayla gönderilmez. .crt uzantılı dosyalar ise zaten herkese gösterilmek için var.
Birinci terminalde bu komutu çalıştır ve açık bırak. Sunucu 4433 portunu dinleyecek.
openssl s_server \
-accept 4433 \
-cert server.crt \
-key server.key \
-CAfile ca.crt \
-Verify 1 \
-www
-Verify (büyük V) zorunlu demektir — sertifika yoksa bağlantı reddedilir. -verify (küçük v) opsiyonel demektir — istemci sertifika gösterirse doğrularım, göstermezse de bağlanırım. mTLS için büyük V lazım.
İkinci terminalde bu testleri sırayla dene. Her testten sonra bağlantıyı kapatmak için Ctrl+C.
echo "GET / HTTP/1.0" | openssl s_client \
-connect localhost:4433 \
-cert client.crt \
-key client.key \
-CAfile ca.crt \
-quiet
✓ Bağlantı kurulur, sunucudan HTML yanıtı gelir. Karşılıklı doğrulama başarılı.
openssl s_client \
-connect localhost:4433 \
-CAfile ca.crt
✗ tlsv13 alert certificate required veya peer did not return a certificate hatası. Sunucu "sen sertifika göstermek zorundasın" diyor.
Önce sahte bir CA ve onunla imzalı bir istemci sertifikası üret:
# Sahte CA
openssl genrsa -out fake-ca.key 2048
openssl req -new -x509 -key fake-ca.key -out fake-ca.crt -days 365 \
-subj "/CN=Sahte CA"
# Sahte CA ile imzalı istemci
openssl genrsa -out fake-client.key 2048
openssl req -new -key fake-client.key -out fake-client.csr \
-subj "/CN=sahte-musteri"
openssl x509 -req -in fake-client.csr \
-CA fake-ca.crt -CAkey fake-ca.key -CAcreateserial \
-out fake-client.crt -days 365 \
-extfile client_ext.cnf
Şimdi bu sahte sertifikayla bağlanmayı dene:
openssl s_client \
-connect localhost:4433 \
-cert fake-client.crt \
-key fake-client.key \
-CAfile ca.crt
✗ tlsv1 alert unknown ca hatası. Sunucu "bu sertifikayı imzalayan CA benim güven listemde yok" diyor. Çünkü sunucu -CAfile ca.crt ile başladı, sadece o CA'ya güveniyor. fake-ca.crt orada yok.
Test 1 çalıştığında ağ üzerinde ne gidip geldi? mTLS'e özgü adımlar sarı ile işaretli.
1. İstemci ClientHello gönderir → client_random, cipher suite listesi, TLS versiyonu 2. Sunucu ServerHello gönderir → server_random, seçilen cipher suite 3. Sunucu kendi sertifikasını gönderir (server.crt) → içinde sunucunun açık anahtarı 4. Sunucu CertificateRequest gönderir ← mTLS EK ADIMI → "senin de sertifikanı istiyorum" → hangi CA'lardan imzalı olması gerektiğini söyler 5. Sunucu ServerHelloDone gönderir 6. İstemci sunucu sertifikasını doğrular: → server.crt'yi ca.crt ile doğrular → imza tutuyor mu? ✓ → süre geçerli mi? ✓ → hostname SAN'da var mı? ✓ 7. İstemci kendi sertifikasını gönderir (client.crt) ← mTLS EK ADIMI 8. İstemci ClientKeyExchange gönderir → pre-master secret'i sunucunun açık anahtarıyla şifreleyip yollar 9. İstemci CertificateVerify gönderir ← mTLS EK ADIMI → şimdiye kadarki handshake mesajlarının hash'ini kendi gizli anahtarıyla imzalar → "sertifikadaki açık anahtarın eşi olan gizli anahtar gerçekten bende" kanıtı 10. Sunucu istemci sertifikasını doğrular: → client.crt'yi ca.crt ile doğrular → imza tutuyor mu? ✓ → CertificateVerify imzası tutuyor mu? ✓ → extendedKeyUsage = clientAuth var mı? ✓ 11. Her iki taraf session key türetir 12. Finished mesajları 13. Şifreli uygulama verisi akmaya başlar
Bu adım olmasa, biri başkasının sertifikasını (public bilgi olduğu için) çalıp "bak ben oyum" diyebilirdi. CertificateVerify, istemcinin gerçekten o sertifikanın eşi olan gizli anahtara sahip olduğunu matematiksel olarak kanıtlar — çünkü o imzayı sadece gizli anahtarın sahibi üretebilir.
mTLS'te -CAfile hem sunucu hem istemci tarafında var ama anlamları farklı.
| Taraf | Neyi Doğruluyor | Hangi Dosyayla |
|---|---|---|
| İstemci | Sunucunun sertifikasını | ca.crt (istemci tarafında) |
| Sunucu | İstemcinin sertifikasını | ca.crt (sunucu tarafında) |
Bizim örneğimizde aynı CA her ikisini imzaladı, o yüzden aynı ca.crt kullanılıyor. Gerçekte iki farklı CA olabilir — mesela şirket içi CA istemcileri imzalar, Let's Encrypt sunucuyu imzalar. O zaman sunucu -CAfile sirket-ca.crt, istemci -CAfile letsencrypt-ca.crt kullanır.
Normal TLS'te sadece sunucu kendini kanıtlar, istemci anonim kalır. mTLS'te iki taraf da sertifika gösterir ve gizli anahtarlarına sahip olduklarını kanıtlar — yani iki taraf da birbirinin gerçekten iddia ettiği kişi/servis olduğundan matematiksel olarak emin olur.