PKIX path building failed to popularny problem w środowisku Java. Zwykle objawia się nam przy różnych integracjach. A jeszcze częściej, gdy coś sobie z instalacją Javy zmienimy… bo przecież lubimy czasem zmienić…
PKIX path building failed
Kto programuje w Javie i nigdy nie trafił na:
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: PKIX path building failed
niech pierwszy rzuci kamieniem.
W uproszczeniu chodzi o to, że Twoja aplikacja chce połączyć się z inną usługą za pomocą SSL/TLS. Ale niestety nie może tego zrobić… bo jej nie ufa.
Zaufanie to słowo klucz w tym przypadku. Zacznijmy jednak od krótkiej historii.
Bo dzień był długi
A skoro dzień dłuży się niemiłosiernie, to co się wówczas robi?
Jeśli w pierwszej kolejności przychodzi Ci do głowy „to może by tak sobie Javę update’nąć?” to wiesz w jakim punkcie byłem kilka dni temu.
Nowa Java super sprawa. Postanowiłem więc ściągnąć nową wersję. Jak postanowiłem, tak zrobiłem. A skoro powiedziało się A, to trzeba też powiedzieć B. W tym przypadku oznaczało to uruchomienie mojej aplikacji serwerowej z Java 19 od Amazon.
I w zasadzie wszystko szło dobrze. Spring Boot ślicznie się odpalił. Wszystko działało.
No… prawie wszystko. Moja apka zaraz po starcie wykonuje synchronizację z zewnętrzną usługą. Taki zwykły pseudo-REST po HTTPS.
Dla przypomnienia HTTPS, to protokół HTTP chroniony przez TLS (kiedyś SSL). A skoro chronione, to mówimy o różnych certyfikatach.
I tu klasycznie. Nowa Java. Czyli nowe magazyny certyfikatów. Czyli wywołanie kończy się jednym z ulubionych PKIX path building failed i SSLHandshakeException.
Zaufaj mi w cacerts
Wróćmy na chwilę do zaufania.
W środowisku IT zaufanie to certyfikaty. Zacznijmy od tego, że w Java mamy dwa typy magazynów certyfikatów.
Pierwszy to key store. Zwykle zawiera informacje identyfikujące nas, np. certyfikaty naszych serwerów. Dziś nie będziemy się nim specjalnie zajmować.
Drugi to trust store. Czyli magazyn certyfikatów, którym my ufamy. Jeśli chcemy korzystać z jakiejś usługi z SSL/TLS to powinniśmy jej ufać. A to znaczy, że powinniśmy w naszym magazynie trust store posiadać certyfikaty identyfikujące usługi, z których chcemy korzystać.
I to właśnie brak odpowiednich certyfikatów w trust store powoduje błąd PKIX path building failed i wyjątek javax.net.ssl.SSLHandshakeException.
Fajnie, ale gdzie jest ten cały trust store❓
Java dostarcza go razem z JDK. I możesz znaleźć go zwykle pod taką ścieżką:
$JAVA_HOME/jre/lib/security/cacerts
Kiedy trafisz na PKIX path building failed
W praktyce PKIX path building failed trafić możesz w bardzo wielu sytuacjach. Kilka przykładów:
- Zmieniasz Javę. Bo fajnie jest zmienić Javę. I zapominasz o cacerts.
- Masz Maven 3.6 i wpadasz na pomysł, że warto jednak skorzystać z nowszej wersji. A tu zonk. Bo nowsze wersje nie lubią http. Lubią https. I fajnie, bo samo repozytorium artefaktów daje https, ale nie masz jego certyfikatu u siebie w cacerts.
- Zewnętrzna usługa, z której korzystasz, ma niepoprawny lub nieważny certyfikat SSL/TLS.
- Ta sama usługa ma dla Ciebie nieznany lub niewłaściwy certyfikat CA.
- Certyfikat takiej usługi lub CA wygasł. Alternatywnie administrator źle go wygenerował.
Keytool na ratunek
Skoro masz JDK to masz też małe narzędzie keytool.
Keytool służy do zarządzania magazynem kluczy kryptograficznych, łańcuchów certyfikatów X.509 i zaufanych certyfikatów.
Załóżmy przez chwilę, że Twoja Java chciałaby skorzystać z jakiejś usługi. Ale niestety nie masz w swoim magazynie certyfikatu tej usługi. I dostajesz PKIX path building failed.
Nic strasznego. Masz pewność, że chcesz skorzystać z tej usługi. Ufasz jej. Dlatego postanawiasz dodać jej certyfikat do swojego magazynu trust store.
Żeby dodać sobie jej certyfikat do magazynu wystarczy go mieć lub wyeksportować, np. do pliku *.der. A to łatwo zrobić w przeglądarce. Poniżej przykład, jak w łatwy sposób zrobić to dla referencyjnej wyszukiwarki Google.
Jak widać powyżej eksport certyfikatu jest bardzo prosty. Jeszcze prościej, jak mamy go w jakimś repozytorium.
Bardzo dużo ludzi, zwłaszcza na początku nauki programowania, nie lubi narzędzi obsługiwanych z linii poleceń. Ale nie ma się co bać. Prosta komenda rozwiązuje sprawę:
keytool -import -alias nasz-alias-dla-certyfikatu -keystore $JAVA_HOME/jre/lib/security/cacerts -file $HOME/nasz-wyeksportowany-zaufany-certyfikat.der
Powyżej wymyślamy alias dla certyfikatu. To tylko taka krótka nazwa, żeby ładnie było widać w magazynie. Wskazujemy nasz trust store (tak, opcja nazywa się tu keystore, bo obsługuje się je tak samo). I wskazujemy nasz plik *.der z certyfikatem danej usługi.
Keytool zapyta nas o hasło. Jeśli mamy domyślne, to wpisujemy changeit.
Możemy sprawdzić, czy się udało:
keytool -list -alias nasz-alias-dla-certyfikatu -keystore $JAVA_HOME/jre/lib/security/cacerts
Słowo końcowe
Liczę, że ten artykuł sprawi, że już nigdy PKIX path building failed i javax.net.ssl.SSLHandshakeException nie przysporzy Ci większych problemów.
Na koniec prośba do Ciebie o pozostawienie komentarza, ocenienie artykułu poniżej lub przesłanie go dalej. Niech wiedza leci w świat ❤️
z początku pretensjonalnie, ale okazało się że wartość merytoryczna wysoka, bije na głowę całą wartość mertytoryczną z 4 lat ostatnich obradów Sejmu RP
Z jakiegoś powodu certyfikat wyeksportowany z przeglądarki dalej rzuca błędy. Pomógł eksport za pomocą openssl