PEM'den Çalışan Stack'e — Bölüm 2: Java'nın Okumayı Reddettiği Keystore
Serinin 2. bölümü. 1. bölümde sertifika formatlarını çözdük. Şimdi onlardan PKCS#12 truststore oluşturuyoruz — ve dosya teknik olarak doğru olduğu halde bile iki engelle karşılaşıyoruz.
Keycloak (Java tabanlı bir kimlik doğrulama sunucusu) bir PKCS#12 truststore istiyordu: servisin güvenmesi gereken public CA sertifikasını tutan bir dosya. Truststore, kasıtlı olarak özel anahtar içermez — yalnızca bir CA'nın public sertifikasını barındırır. Bununla servise şunu söylüyorsunuz: "Bu CA tarafından imzalanan her şeye güvenebilirsin."
Temiz CA sertifikam hazırdı. Akla gelen ilk komut:
openssl pkcs12 -export -nokeys -in ca.crt -out root.p12 -passout pass:<TRUSTSTORE_PW>
-nokeys, çünkü truststore'un anahtara ihtiyacı yok. Çalışması gerekiyordu. Ve çalıştı — görünürde.
Dosya oluşturulmuştu, doğru parolaya sahipti ve OpenSSL ile açılıyordu. Ama Java, truststore'un boş olduğunu bildirdi. Kanıt, Java'nın kendi aracı keytool ile:
keytool -list -keystore root.p12 -storetype PKCS12 -storepass <TRUSTSTORE_PW> # -> Your keystore contains 0 entries
KANIT: sıfır girdi — OpenSSL dosyayı oluştururken şikâyet etmediği ve incelendiğinde düzgünce bir Certificate bag gösterdiği hâlde.
Nedenin inceliği şu: openssl pkcs12 -export -nokeys ile ilişkili özel anahtarı olmayan salt bir sertifikayı paketlediğinizde, sertifika kaba alias (diğer adıyla "friendlyName") olmadan yerleşir. Java/keytool ise alias'sız girdileri kullanılabilir girdi olarak listelemiyor. Java'nın gözünden bakınca: boş.
Çözüm, truststore'u Java'nın kendi aracıyla oluşturmak — çünkü keytool -importcert tam da bunun için var: güvenilen bir sertifikayı alias ile birlikte içe aktarmak:
keytool -importcert -noprompt \ -alias corp-ca \ -file ca.crt \ -keystore root.p12 \ -storetype PKCS12 \ -storepass <TRUSTSTORE_PW>
Artık keytool -list düzgünce 1 entry ve trustedCertEntry gösteriyor. Java sertifikayı görüyor.
HÜKÜM: Java truststore'ları için doğru araç keytool'dur, OpenSSL export değil. OpenSSL'in geçerli saydığı şeyin Java dünyasında kullanılabilir olacağı kesinlikle garanti değildir.
Boş keystore sorunu çözülür çözülmez bir sonraki klasik sahneye geçildi:
ERROR: keystore password was incorrect javax.crypto.BadPaddingException: Given final block not properly padded
Sinsi olan kısım şu: aynı dosyayı OpenSSL ile ve tam bu parola ile sorunsuz açabiliyordum:
openssl pkcs12 -info -in root.p12 -passin pass:<TRUSTSTORE_PW> -noout # runs through cleanly
KANIT: OpenSSL dosyayı parola ile açarken Java "yanlış parola" diyorsa sorun parola değil — şifreleme algoritması.
Arka plan: OpenSSL 3.x, PKCS#12 dosyalarını varsayılan olarak modern PBES2 / AES-256 / SHA-256 ile paketliyor. Birçok container imajındaki JDK 17 gibi eski Java sürümleri ise bu algoritmayı çözerken anlayamıyor — ve yanıltıcı biçimde "parola yanlış" diyor, oysa parola tastamam doğru.
Modern formatı OpenSSL çıktısından anlayabilirsiniz:
openssl pkcs12 -info -in root.p12 -passin pass:<TRUSTSTORE_PW> -noout # PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC ... <- Java doesn't like this # MAC: sha256
Java ise daha eski legacy formatını istiyor:
PKCS7 Encrypted data: pbeWithSHA1And3-KeyTripleDES-CBC ... <- Java understands this MAC: sha1
HÜKÜM: OpenSSL dosyayı sorunsuz açarken Java'dan "parola yanlış" geliyorsa bu neredeyse her zaman yanlış PKCS#12 şifreleme algoritması anlamına gelir — yanlış parola değil.
Catch-22 — ve çözümü
Böylece projenin tamamına yayılan bir kısır döngüye girmiştim:
- OpenSSL export, legacy formatta → Java tarafından okunabilir ama boş keystore (alias yok).
- keytool import → alias'lı dolu bir keystore, ama Java'nın reddettiği modern AES formatında.
Her ikisini aynı anda istiyordum: alias'lı bir girdi ve legacy format.
Çözüm, oluşturma için keytool kullanmak (alias sorununu çözüyor) ve aynı zamanda JDK sistem özelliklerini kullanarak legacy formatı zorlamaktı:
keytool -importcert -noprompt -alias corp-ca \ -file ca.crt \ -keystore root.p12 -storetype PKCS12 -storepass <TRUSTSTORE_PW> \ -J-Dkeystore.pkcs12.certProtectionAlgorithm=PBEWithSHA1AndDESede \ -J-Dkeystore.pkcs12.certPbeIterationCount=2048 \ -J-Dkeystore.pkcs12.macAlgorithm=HmacPBESHA1 \ -J-Dkeystore.pkcs12.macIterationCount=2048
Dört -J-D seçeneği "eski 3DES/SHA1 formatını kullan" talimatını keytool'u çalıştıran JVM'e iletir. Sonuç: alias'lı, Java'nın okuyabileceği formatta bir girdi. İki koşul tek hamlede karşılandı.
Dosyanın container içinde oluşturulması gerektiğinde
Pratik bir yan engel daha: hangi keytool'u kullandığınız önemsiz değil. En güvenilir yöntem, keystore'u onu daha sonra okuyacak olan Java ile oluşturmak — yani container'ın içinde. Bu şekilde üretilen formatın uyumlu olacağı garanti altına alınmış olur.
Container'daki sertifika dizini genellikle salt okunur (read-only) bağlandığından, dosyayı /tmp'ye oluşturup dışarı akıtmak gerekiyor:
docker compose run --rm -v /host/certs:/out:ro --entrypoint sh keycloak -c '
keytool -importcert -noprompt -alias corp-ca \
-file /out/ca.crt \
-keystore /tmp/root.p12 -storetype PKCS12 -storepass <TRUSTSTORE_PW> \
-J-Dkeystore.pkcs12.certProtectionAlgorithm=PBEWithSHA1AndDESede \
-J-Dkeystore.pkcs12.macAlgorithm=HmacPBESHA1 && cat /tmp/root.p12
' > /host/certs/root.p12
Numara şu: keytool /tmp'ye yazar (her zaman yazılabilir), sonucu cat ile standart çıktıya döker, host'taki shell ise bunu > ile hedef dosyaya yönlendirir — yazma izni container'da değil, sizde var.
Bölüm 2'den Öğrendiklerim
- Başarılı oluşturmaya rağmen "0 entries" = sertifikanın alias'ı yok.
keytool -importcert'i-aliasile kullanın. - Parola doğru olduğu hâlde "parola yanlış" = aslında bir algoritma sorunu. OpenSSL 3 AES-256 yazar, eski Java okuyamaz.
- Java legacy formatı ister:
pbeWithSHA1And3-KeyTripleDES-CBC/MAC: sha1. keytool+-J-Dseçenekleri tek seferde hem dolu hem legacy üretir.- Doğrulamayı yalnızca OpenSSL ile değil,
keytoolile yapın — yalnızca o Java'nın bakış açısını gösterir. - Keystore'ları hedef container içinde oluşturun, böylece format uyumluluğu garanti olur.
Sonraki bölümde iş organizasyonel olarak tehlikeli bir hal alıyor: birden fazla servis aynı dosya için farklı parolalar beklediğinde ne oluyor — ve üç farklı parola değişkenini birbirinden nasıl ayırt edebilirsiniz.