Wywołanie z self-signed certificate

0

API do którego podłączam się w środowisku testowym wymaga określonego certyfikatu od klienta. Certyfikat jest self-signed, przez co otrzymuję
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Certyfikat otrzymałem od wystawcy w .pfx, następnie używając
keytool -importkeystore -srckeystore cert_dev.pfx -srcstoretype pkcs12 -destkeystore clientcert.jks -deststoretype JKS
utworzyłem keystore.

konfiguracja WebServiceTemplate

    @Bean
    public WebServiceTemplate webServiceTemplate() throws Exception {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setMarshallerProperties(Map.of(JAXB_FORMATTED_OUTPUT, true));

        WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
        webServiceTemplate.setDefaultUri(properties.getBaseUrl());
        webServiceTemplate.setMessageSender(sender());
        webServiceTemplate.setMarshaller(marshaller);
        webServiceTemplate.setUnmarshaller(marshaller);

        return webServiceTemplate;
    }

    @Bean
    public HttpsUrlConnectionMessageSender sender() throws Exception {
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(
                properties.getAuthentication().getKeyStore().getInputStream(),
                properties.getAuthentication().getKeyStorePassword().toCharArray()
        );

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(ks, properties.getAuthentication().getKeyStorePassword().toCharArray());

        KeyStore ts = KeyStore.getInstance("JKS");
        ts.load(
                properties.getAuthentication().getTrustStore().getInputStream(),
                properties.getAuthentication().getTrustStorePassword().toCharArray()
        );

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(ts);

        HttpsUrlConnectionMessageSender messageSender = new HttpsUrlConnectionMessageSender();

        messageSender.setKeyManagers(keyManagerFactory.getKeyManagers());
        messageSender.setTrustManagers(trustManagerFactory.getTrustManagers());
        messageSender.setHostnameVerifier((hostname, sslSession) -> {
            return true;
        });

        return messageSender;
    }

przy każdym wywołaniu otrzymuję

Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

czy to dlatego, że certyfikat jest self signed?

dodam, że przez SoapUI działa bez problemu.

3

W skrócie: do truststore dodaj CA, które podpisało certyfikat serwera.

Certyfikat kliencki został pewnie wygenerowany dla Ciebie i podpisany przez to CA. Ty masz jedynie okazać cert, a serwer zapewne już ufa temu CA.

Wyjątek leci podczas procesu "handshake". Treść komunikatu unable to find valid certification path to requested target
sugeruje, że nie chodzi o certyfikat kliencki, tylko weryfikację certyfikatu okazanego przez serwer (requested target)

Podczas procesu handshake, serwer okazuje swój certyfikat, w nim zawarta jest informacja "certyfikat podpisany przez Certification Authority#1, CA#1 podpisane przez CA#2 itd.".
Dostajesz więc: certyfikat + ścieżkę certyfikacji. Jeśli certyfikat ma być traktowany jako zaufany, to cała ścieżka certyfikacji powinna być zaufana.

Dlaczego cała?

Google CA -> Firma CA -> Security dupa CA -> Hakerskie CA -> fejkowy certyfikat serwera ->ufasz Top-Level rootCA (google), ale na ścieżce powstał problem, bo zostało stworzone Hakerskie CA i generuje fałszywe certyfikaty.

W praktyce dla samo-podpisanych certów, ścieżka certyfikacji jest krótka: RootCA -> jakiś certyfikat, więc wystarczy to RootCA dodać do truststore.

0
yarel napisał(a):

W skrócie: do truststore dodaj CA, które podpisało certyfikat serwera.

Certyfikat kliencki został pewnie wygenerowany dla Ciebie i podpisany przez to CA. Ty masz jedynie okazać cert, a serwer zapewne już ufa temu CA.

Wyjątek leci podczas procesu "handshake". Treść komunikatu unable to find valid certification path to requested target
sugeruje, że nie chodzi o certyfikat kliencki, tylko weryfikację certyfikatu okazanego przez serwer (requested target)

Podczas procesu handshake, serwer okazuje swój certyfikat, w nim zawarta jest informacja "certyfikat podpisany przez Certification Authority#1, CA#1 podpisane przez CA#2 itd.".
Dostajesz więc: certyfikat + ścieżkę certyfikacji. Jeśli certyfikat ma być traktowany jako zaufany, to cała ścieżka certyfikacji powinna być zaufana.

Dlaczego cała?

Google CA -> Firma CA -> Security dupa CA -> Hakerskie CA -> fejkowy certyfikat serwera ->ufasz Top-Level rootCA (google), ale na ścieżce powstał problem, bo zostało stworzone Hakerskie CA i generuje fałszywe certyfikaty.

W praktyce dla samo-podpisanych certów, ścieżka certyfikacji jest krótka: RootCA -> jakiś certyfikat, więc wystarczy to RootCA dodać do truststore.

dzięki za sugestię. w ślad za rozwiązaniem, które sugerujesz wyodrębniłem

openssl pkcs12 -in mycert.pfx -clcerts -nokeys -out pub.cer

openssl pkcs12 -in mycert.pfx -cacerts -nokeys -chain -out ca.cer

następnie utworzyłem keystore i truststore

keytool -import -alias pub -file pub.cer -storetype JKS -keystore pub.jks 

keytool -import -alias ca -file ca.cer -storetype JKS -keystore trust.jks

oba pliki wrzuciłem do resources, które były zaczytywane jak w configu w wiadomości wyżej, tj

    @Bean
    public HttpsUrlConnectionMessageSender sender() throws Exception {
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(
                properties.getAuthentication().getKeyStore().getInputStream(),
                properties.getAuthentication().getKeyStorePassword().toCharArray()
        );

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(ks, properties.getAuthentication().getKeyStorePassword().toCharArray());

        KeyStore ts = KeyStore.getInstance("JKS");
        ts.load(
                properties.getAuthentication().getTrustStore().getInputStream(),
                properties.getAuthentication().getTrustStorePassword().toCharArray()
        );

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(ts);

        HttpsUrlConnectionMessageSender messageSender = new HttpsUrlConnectionMessageSender();

        messageSender.setKeyManagers(keyManagerFactory.getKeyManagers());
        messageSender.setTrustManagers(trustManagerFactory.getTrustManagers());
        messageSender.setHostnameVerifier((hostname, sslSession) -> {
            return true;
        });

        return messageSender;
    }

niestety nadal błąd walidacji.

czy generuję pliki jks we właściwy sposób? próbowałem pub i ca wrzucić do jednego keystora, efekt był niestety ten sam.

1

@hopsey: sprawdź czy łańcuch certyfikacji dla serwera i klienta są takie same. Czasem jest tak, że certyfikaty klienckie podpisywane są przez inne CA niż te serwera.
Z tego co wdzię, mycert.pfx zawiera certyfikat kliencki + CA podpisujące certyfikat kliencki. Może serwer okazuje certyfikat podpisany przez inne CA?

Łatwo to zweryfikować (na przykładzie google.com jako serwer):
openssl s_client -showcerts -connect google.com:443 2>&1 | grep depth

depth=3 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
depth=0 CN = *.google.com
^C

Tu masz 3 certyfikaty CA do zaimportowania (certyfikaty google i innych dużych graczy są domyślnie dystrybuowane z systemami operacyjnymi, przeglądarkami itp.)

Jeśli CA z certyfikatu klienckiego jest inne niż serwera, to połącz się z serwerem i zapisz to co okazuje, a następnie zaimportuj do trusstore'a.

Zapis do PEMa:
openssl s_client -showcerts -connect twoj.serwer:443 </dev/null 2>/dev/null|openssl x509 -outform PEM >server.pem

1 użytkowników online, w tym zalogowanych: 0, gości: 1