HTTPS sitemde neden artık eski usul bir sertifika kullanmıyorum
(rachelbythebay.com)- Yazar, ACME protokolünün karmaşıklığı ve uygulama riskleri nedeniyle yıllarca buna mesafeli durmuş
- Mevcut ACME istemcilerinin çoğunda güvenlik açısından riskli veya anlaşılması zor kodlar bulunduğundan bunları doğrudan çalıştırmaya çekinmiş
- Ancak alan adı kayıt şirketi Gandi'nin kalite düşüşü ve fiyat artışları nedeniyle sertifika yenileme aracını kendisi uygulamaya karar vermiş
- Sayısız deneme-yanılma sonunda Let's Encrypt üzerinden doğrudan sertifika alan bir aracı başarıyla tamamlamış
- Yazının ikinci yarısında ACME protokolünün gerçek çalışma süreci ile JSON, base64, imza gibi düşük seviyeli uygulama ayrıntılarını ayrıntılı biçimde anlatıyor
Why I no longer have an old-school cert on my https site
Arka plan ve motivasyon
- 2023 başında neden eski usul sertifikayı sürdürdüğünü açıklamıştı; ancak 2025 itibarıyla artık bu yöntemi neden bıraktığını paylaşıyor
- ACME protokolüne karşı mesafesi 2018'den beri vardı; karmaşık web teknolojileri ve anlaşılması güç kodlama biçimleri büyük bir engeldi
- ACME istemcilerinin çoğu güven vermeyen kodlardan oluşuyordu ve bunları root yetkisiyle çalıştırmayı riskli buluyordu
- Gandi özel sermaye fonuna satıldıktan sonra kalite düştü ve fiyatlar arttı; böylece eski sertifikayı sürdürmek için bir neden kalmadı
Kendi uygulamasına başlama
- Mevcut araçları kullanmak yerine, doğrudan küçük yardımcı işlevleri tek tek kendisi yazmaya başlamış
- İlk adım olarak, C için olan
janssonJSON kütüphanesini C++ içinde kullanılabilir hale getiren bir sarmalayıcı yazmış - JWK(Key yapısı) üretimi için çeşitli kütüphaneleri incelemiş, ancak çoğu işe yaramayınca bunu da kendisi yazmaya karar vermiş
- Arada birkaç kez durup yeniden başlama sürecini tekrarlayarak, giderek küçük bileşenleri birbirine bağlamış
Test ortamı ve gerçek kullanım
-
Let's Encrypt'in gerçek sunucularına doğrudan dokunmamak için, bağımsız bir ortamda
pebbleadlı test amaçlı ACME sunucusunu kullanmış -
Sayısız başarısız denemeden sonra, CSR girdisi alıp sertifika veren ilk sürüm bir aracı tamamlamış ve
- Let's Encrypt staging sunucusunda test başarıyla geçmiş
- Üretim ortamında da çalışmış
- Gerçek web sitesine de uygulanmış
ACME protokolünün ayrıntılı açıklaması
- RSA anahtarı üretip CN ve SAN içeren bir CSR(Certificate Signing Request) oluşturuyor
- ACME dizin URL'sinden JSON ayrıştırarak
newNonce,newAccount,newOrdergibi uç noktaları çıkarıyor - Özel anahtardan modulus ve public exponent çıkarıp, bunları web'e uygun base64url kodlamasına dönüştürüyor
- JWK oluşturduktan sonra, JSON payload ile birlikte RSA SHA256 imzası üretiyor
- HTTP HEAD isteğiyle Nonce aldıktan sonra, imzalı isteği POST ile göndererek hesap oluşturuyor
- Yanıttaki
Locationbaşlığı gerçek bir yönlendirme değil, hesap tanımlayıcı URL'si olarak kullanılıyor
ACME protokolünün karmaşıklığı
- Basit bir sertifika verme işlemi gibi görünse de,
- SHA256 hash, base64web, JSON içinde JSON yapısı, RSA imzası
- HEAD isteği,
Locationbaşlığıyla hesap tanımlama, tek kullanımlık Nonce gereksinimi gibi unsurlar içeriyor
- Henüz sertifika siparişi, alan adı sahipliğinin kanıtlanması (TXT kaydı vb.), doğrulamanın tamamlanması gibi bölümlere daha hiç gelinmediğini belirtiyor
- Bazı istemcilerin
publicExponentkodlamasını yanlış uygulasa bile çalışabildiğini söyleyerek, standardın gevşekliğine de dikkat çekiyor
Sonuç
- ACME son derece karmaşık ve elle uygulamak için çok fazla deneme-yanılma ile emek gerektiren bir sistem
- Buna rağmen eski usul sertifikayı bırakıp tam otomatik yaklaşıma geçişi başarıyla tamamladığını paylaşıyor
- Bu karmaşıklığın belki de birilerinin işini güvence altına almak için kurulmuş bir yapı olup olmadığına dair şaka da yapıyor
1 yorum
Hacker News görüşü
Ben Let’s Encrypt SRE/infra ekibinin teknik lideriyim; bu tür sorunlar hakkında çok kafa yoran bir konumdayım.
JSON Web Signature gerçekten oldukça çetrefilli bir format ve ACME API de RESTful olma konusunda fazlasıyla ısrarcı.
Ben kendim tasarlasaydım bunu böyle yapmazdım.
Bu yapının ortaya çıkmasında IETF’nin IETF standartlarını bolca kullanma isteğinin ve komite usulü tasarımın payı olduğunu düşünüyorum.
Sadece birkaç JSON, JWS ve HTTP kütüphanesiyle işler epey kolaylaşıyor, ama özellikle C tarafında bu kütüphaneleri kullanmak bile kolay değil.
RFC dili de başlı başına karmaşık ve sık sık başka belgelere atıf yapıyor; bunu kolaylaştırmak için ayrıca etkileşimli istemci ve dokümantasyon üzerinde çalışıyoruz.
JSON Web Signature’ın çetrefilli bir format olduğu yorumunu pek anlayamıyorum.
ASN.1, Kerberos, PKI gibi karmaşık şeylerle çok uğraşan biri olarak, JWS’nin o kadar da zor bir format olduğunu düşünmüyorum.
Hatta elde kod yazmak gerekse bile S/MIME, CMS, Kerberos vb. şeylerden çok daha kolay olduğunu söyleyebilirim.
JWS’nin tam olarak nerede “çetrefilli” olduğunu biraz daha açmak gerekir.
Sorun JWT ise, asıl mesele bence HTTP user agent’larının JWT’yi standart biçimde nasıl alacağı ya da isteyeceğinin iyi tanımlanmamış olması.
Birinin “3’ten fazla sertifika almak için para ödemen gerekiyor” dediğini gördüm, ama ben bunu son 5 yıldır kullanıyorum ve hiç fatura almadım; bu bir yanlış anlama ya da düpedüz yanlış bilgi gibi görünüyor.
“e=65537” yerine “e=AQAB” kullanılmasından söz ederken, bunun sebebinin JSON’ın sayıları düzgün ele alamayan yapısı olduğu anlatılıyor.
Çok büyük bir sayı olan 4723476276172647362476274672164762476438 gibi bir değeri JSON parser’a verdiğinizde, çoğu parser bunu sessizce 64 bit tamsayıya ya da float’a kırpıyor; şanslıysanız hata veriyor.
Common Lisp gibi diller bunu iyi idare ederdi, ama pratikte o tür ortamlarda geliştirme yapan kişi sayısı az.
Bu yüzden JSON içinde büyük sayıları güvenilir biçimde taşımak için onları base64 ile byte dizisine çevirmek daha mantıklı olabilir.
Yüzeyde sorunsuz çalışıyor gibi görünse de, bu durum çeşitli güvenlik sorunlarının kaynağı olabiliyor; dolayısıyla protokoldeki tüm sayıları bu şekilde ele almak makul görünüyor.
Yine de bunun bedeli JSON’ın insan dostu okunabilirliğini kaybetmesi ve ben şahsen standartlaştırılmış S-Expression’ın çok daha iyi bir tercih olduğunu düşünüyorum.
Ama dünya JSON’ı seçti.
Dünyanın neden JSON’ı seçtiğini anlamıyorsanız, bunu bilerek görmezden geliyorsunuz demektir.
JSON çoğu veri türü için insanların kolayca elle yazıp düzenleyebileceği ve okuyabileceği bir yapı sunuyor.
Buna karşılık Canonical S-Expression’da her öğenin başına uzunluk bilgisi koymak gerekiyor, bu da elle çalışmayı çok zahmetli hale getiriyor.
S-Expression yazarken tek tek karakter sayıp önekleri güncellemek zorunda kalmak gerçekten can sıkıcı.
Sanılanın aksine, JSON’ın ayakta kalmasının sebebi işte bu kolay el ile yazılabilirlik ve düzenlenebilirlik.
Bu arada Ruby JSON parser’ı büyük sayıları da düzgün işliyor.
C# uygulamamda JSON serializer’ın BigInt’i sayı olarak dışa verdiği ve JS tarafının bunu sessizce yanlış yorumladığı bir hatayla uğraşmıştım.
Hata vermek yerine overflow’un standart davranış olması beni hâlâ şaşırtıyor.
O günden beri 32 bitten büyük tüm sayıları mutlaka string olarak ele alıyorum.
{"e":"AQAB"} ile {"e":65537} karşılaştırmasının bir mantığı var, ama bunu {"e":"65537"} ile kıyaslarsanız tüm JSON parser’larda sonuç yine aynıdır.
İster sayı ister string olsun, dönüşüm nettir.
Elbette sonuç double’a sığmayacak kadar büyükse bu başlı başına bir dil ya da parser sorunudur, ama temsil biçiminden ayrı bir konudur diye düşünüyorum.
JSON’daki sorun formatın kendisi değil, parser’ların başlangıçta JS tip eşlemesi için tasarlanmış olması.
Bazı parser’lar bunu düzgün ele alabiliyor olsa da, öyle yaptığınız anda JSON’ın taşınabilirliği kayboluyor.
Base64’e çevirmek de aynı problemi yaratıyor (çünkü standart dışına çıkmış oluyorsunuz).
replacer ve reviver ile özel parse işlemi yapılabilir, ama bunun her ortamda garanti olduğunu söyleyemezsiniz.
Sonuçta hatanın kaynağı, JSON’ın standart parser’la çözümleneceği varsayımı.
Buna JSON değil de başka bir format deseydiniz bu sorunlar azalırdı, ama insanlar yine JSON’a benziyorsa doğrudan parser’a vermeye çalışacaktı.
Go dili, json.Number tipi sayesinde sayıları kayıpsız biçimde string olarak decode edebiliyor.
Neredeyse en sevdiğim arbitrary decimal tiplerinden birini de paylaşayım: https://github.com/ncruces/decimal?tab=readme-ov-file#decimal-arithmetic
Yarı şaka yarı ciddi, bu durumda S-Expression’ın neden daha iyi olduğunu pek göremiyorum.
Her LISP de arbitrary-precision arithmetic desteklemiyor.
Yazarın ACME’ye ve çeşitli istemcilere neden bu kadar eleştirel yaklaştığı beni şaşırttı.
Bu yalnızca kullanım becerisi eksikliği gibi görünmüyor; daha çok ACME fikrine ya da etrafındaki araçların tamamına karşı bir antipati var gibi hissettim.
Biz de 2019’dan beri birkaç sitede LE tabanlı kurulumlar kullanıyoruz ve bu süreçte çeşitli ACME istemcilerini denedik.
Mesela Crypt-LE bizim kullanım senaryomuz için uygundu; Sectigo ACME ile entegre olmaya çalışırken le64 yetmeyince certbot, lego, posh-acme gibi farklı araçları da kullandık.
Sonunda certbot’ta GHA ortamı sorunlarını düzelterek kullandık ve posh-acme de iyiydi.
Tekrar okuyunca, yazarın sivri tonunun ACME’ye ya da istemcilere değil, bizzat spesifikasyona yönelik olduğunu fark ettim.
Sonuç olarak ACME fikri iyi, ama uygulama ve gerçek hayata aktarımı hayal kırıklığı yaratıyor.
Ben de yazara yakın düşünüyorum.
Yazarın “mevcut istemcilerin çoğu tehlikeli kod ve sunucumda root ayrıcalıklarıyla çalıştıracak kadar güvenilir değiller” sözünü alıntılayayım.
Güvenliğe hassas işlerde bu tür bir temkinlilik bence gayet yerinde.
Orijinal yazının tonunu anlamakta zorlananlara bağlam sunabilecek eski gönderilere bağlantı verilmiş.
Sunucuda ne olduğu anlaşılmayan bir şeyi çalıştırmaktan hoşlanmayan çok insan var; ben de buna sempati duyuyorum.
Ama güvenlik alanı tam bir kedi-fare oyunu ve bu yüzden sürekli değişmek zorunda; eninde sonunda ayak uydurmak gerekiyor.
Neyse ki ACME, kendi istemcinizi yazma özgürlüğü veriyor.
certbot kullanmak zorunda değilsiniz ve TPM gibi kaynaklarınıza kilit koyan bir yapı da değil.
Sıfırdan bir ACME istemcisi yazmaya kalkarsanız, RFC’leri (ve ilgili JOSE vb. belgeleri) doğrudan okumanın sanıldığından daha kolay olduğunu söyleyen biri deneyimini paylaşıyor.
Kendisi bunu uygulamış ve ACME v2 akışını anlamaya yardımcı olan bir özet yazısı da yayımlamış: https://www.arnavion.dev/blog/2019-06-01-how-does-acme-v2-work/
Resmî RFC’lerin yerini tutmaz ama akış şeması ve yöntem bazlı bir dizin gibi kullanılabilir.
MIT güvenlik dersinin final ödevi olarak ACME istemcisi yazmak da var: https://css.csail.mit.edu/6.858/2023/labs/lab5.html
Kılavuzları tek tek okumak yerine, her şeyi İngilizce uzun uzun açıklayan bir yazıyı Hacker News’e koymanın daha çok internet puanı getirmesi gibi tuhaf bir gerçeği iğneliyor.
Web altyapısı protokollerinin giderek daha karmaşık hale geldiğini vurgulayan yazara teşekkür eden biri var.
Bu tür standartlar, sadece araç ya da istemci kullanan geliştiriciler için bile yük oluşturuyor ve “düzenleyici engel” gibi çalışarak sonunda yalnızca mevcut büyük şirketlerin interneti işletme şartlarını karşılayabildiği bir yapı yaratıyor.
ACME tek başına aşılmaz bir giriş bariyeri değil belki, ama bunlar birikince duvara dönüşüyor.
OpenBSD’de, base OS’nin parçası olan çok basit ve hafif bir ACME istemcisi bulunuyor.
Mevcut alternatifler fazla ağır ve Unix felsefesine aykırı olduğu için bunun yazıldığı söyleniyor.
Yazarın bu seçeneği hiç düşünmemiş olması üzücü.
Muhtemelen biraz emekle başka işletim sistemlerine de port edilebilir.
Buna karşılık, bu OpenBSD istemcisinin OpenBSD felsefesinin güvenliğin neden bu kadar karmaşık olduğunu anlayamadığı bir örnek olduğu düşünülüyor.
Bu istemci, yalnızca ilgili makineye kurularak kullanılmak ve bileşenlerin birbirini etkilememesi için ayrıştırılmış bir yapıyla tasarlanmış.
Ama ACME protokolünün kendisi tam bir ayrık yapı kurmaya izin veriyor; web sunucusu, sertifika talep eden sistem ve DNS sunucusu birbirinden tamamen farklı ortamlarda olabilir.
OpenBSD’nin entegre istemcisini kullanmazsanız daha karmaşık olabilir, ama güvenlik tasarım ilkeleri açısından bu yaklaşım daha üstün sayılabilir.
“Sadece OpenBSD kur ve bitsin” yaklaşımı yalnızca kolay bir yol.
uacme (https://github.com/ndilieto/uacme) de öneriliyor.
Hafif bir C kod tabanı var; Let’s Encrypt’in Python istemcisinde pil tüketimi sorunlarıyla çok uğraştıktan sonra bunu istikrarlı bir alternatif olarak kullanmışlar.
OpenBSD ACME istemcisini bizzat kullandığını ve çok iyi çalıştığını söyleyen biri de var.
“4096 bit RSA private key oluşturun” tavsiyesi, pratikte sadece ziyaretçiler için hız kaybı yaratıyor ve gerçek güvenlik açısından 2048 bit düzeyinde kalıyor.
2048 bit leaf sertifika kullanmanın daha iyi olduğu özellikle vurgulanıyor.
4096 bitin pasif trafik kaydı / gelecekte çözme senaryolarına karşı daha dayanıklı olup olmadığını soran biri var.
Ara sertifikaların güvenliğinin de eşzamansız saldırılarda etkili olup olmadığını merak ediyor.
Web hosting sağlayıcısı yalnızca RSA anahtarlarını desteklediği için, EC anahtar desteğini daha hızlı eklemeleri umuduyla bilerek 4096 bit RSA kullandığını söyleyen de var.
Böyle şeyleri kendiniz yapınca gerçekten çok şey öğreniyorsunuz, ama yazarın üslubu daha çok protokole ya da Let’s Encrypt kurulum sürecine sinirleniyor gibi duruyor.
lightweight ACME kütüphaneleriyle (https://github.com/jmccl/acme-lw gibi) bunun yeterince otomatikleştirilebildiğini düşününce neden bu kadar zor yoldan gittiğini merak ediyorum.
Düz alan / bitfield meselelerinin hepsi ASN.1/X.509’un tarihsel mirası; matematiksel karmaşıklık ciddi düzeyde ve tüm kütüphanelerle yazılımlar 80’lerin teknolojik kısıtlarına zincirlenmiş durumda.
Let’s Encrypt geldiğinde ya da HTTP/2 ortaya çıktığında bu kaosu temizlemek için son bir fırsat vardı, ama pratikte ACME CA kurmak shell script + OpenSSL + içki ile bile yapılabildiğinden ve mevcut yazılımlarla uyumluluk da gerektiğinden bu sıçrama gerçekleşmedi.
HTTPS’ye geçiş baskısının giderek arttığına dair deneyimler paylaşılıyor.
Örneğin WhatsApp artık HTTP bağlantılarını açmıyor.
Proxy ve cache kullanarak trafik yükünün azaltılabileceği, bunun küçük sunucular için iyi bir yöntem olduğu öneriliyor.
ACME ne kadar karmaşık olursa olsun, TLS desteği olmamasından çok daha iyi olduğu özellikle belirtiliyor.
“RSA anahtarları, SHA256 digest, RSA imzası, aslında base64 olmayan bir base64, string birleştirme, JSON içinde JSON, 301 redirect yerine tanımlayıcı olarak kullanılan Location header’ı, tek bir header değeri için HEAD isteği, her istek için nonce almak adına ayrı istek gerekliliği gibi katmanlar üst üste geliyor.”
“Üstelik hâlâ sertifika siparişi oluşturma, yetki/challenge işleme, key thumbprint, TXT kaydı oluşturma gibi daha da karmaşık aşamalar var.”
Bunun inanılması güç bir karmaşıklık olduğu ve bu özeti paylaştığı için yazara teşekkür edildiği söyleniyor.