Handshake failure if URL path is specified

I'm trying to use a REST API over HTTPS but I'm getting a handshake failure only if I specify an URL path (not getting the error with the domain name only).

Following the instructions of the API owner, I received a .PEM file (unsigned certificate?) with which I generated a CSR that I sent back, who in turn gave me back a private key file I then encapsulated with the certificate and the signing chain into a PFX file.

I imported the PFX into Internet Explorer certificate store, and I can now reach both the "root" URL (domain name only) ad the API URL (URL path included) without any problem. It is also working in Google Chrome and Postman.

Now, I'm trying to make it work with 3 other applications:

  • CA Technologies - API Management
  • Sofware AG - Integration Server
  • Java 8 through HttpsURLConnection

In every case, I can reach the "root" URL and get a response but I'm getting a handshake failure if I add the path in the URL:

Let's take what I did for each of my applications...

1) CA Technologies - API Management

I imported the PFX in the CA API Gateway with the "Manage Certifactes" import wizard. It added 3 certificate entries: the actual certificate, an intermediate certification authority and a root certification authority configured as a Trust Anchor. The certificate is checked to be used with "Outbound SSL Connections".

But when I use the assertion "Route via HTTPS" to my URL with the path, I get an handshake failure. I also tried to add a certificate to the store by using the "Manage Certificates" add wizard and giving it the URL with the path to download the certificate. It added 3 differents certificates in my store, but it still doesn't work.

2) Software AG - Integration Server

Since this ESB can't read PFX files, I exported the 3 certificates (actual + signing chain) to .CER files via Internet Explorer certificate export. In the WebMethods Integration Server Administrator console, I added the certificates with the "Configure Client Certificates" menu. I also configured the Keystore and the Truststore with their default values (DEFAULT_IS_KEYSTORE & DEFAULT_IS_TRUSTSTORE).

But using the service "pub.client:http" gave me the same error as precedently. I then tried to add my certificates directly inside the keystore of the embarked jvm (%SAG_PATH%/jvm/jvm/jre/lib/security), but it didn't solve the problem.

3) Java 8 through HttpsURLConnection

As a last resort, I tried to make it work directly in "pure" Java. I added the certifactes to my cacerts store and I created a class to connect to my URL and print informations :

HttpsURLConnection con = (HttpsURLConnection) new URL(url).openConnection();

System.out.println("Response Code : " + con.getResponseCode());
System.out.println("Cipher Suite : " + con.getCipherSuite());
System.out.println("\n");

Certificate[] certs = con.getServerCertificates();
for (Certificate cert : certs) {
    System.out.println("Cert Type : " + cert.getType());
    System.out.println("Cert Hash Code : " + cert.hashCode());
    System.out.println("Cert Public Key Algorithm : " + cert.getPublicKey().getAlgorithm());
    System.out.println("Cert Public Key Format : " + cert.getPublicKey().getFormat());
    System.out.println("\n");
}

If I try to access the root URL I get a, OK response :

Response Code : 200
Cipher Suite : TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

Cert Type : X.509
Cert Hash Code : -952876714
Cert Public Key Algorithm : RSA
Cert Public Key Format : X.509

Cert Type : X.509
Cert Hash Code : 272760578
Cert Public Key Algorithm : RSA
Cert Public Key Format : X.509

Cert Type : X.509
Cert Hash Code : -1335658159
Cert Public Key Algorithm : RSA
Cert Public Key Format : X.509

But if I add the path to the actual API, I get an handshake failure too :

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
    at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2023)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1125)
    at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:930)
    at sun.security.ssl.AppInputStream.read(AppInputStream.java:105)
    at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
    at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
    at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
    at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:704)
    at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:647)
    at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:675)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1569)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474)
    at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:338)
    at test.Test.print_https_cert(Test.java:60)
    at test.Test.main(Test.java:23)

Questions

Do I need another certificate for the API actual URL (containing the full path)?
How can it work in my browser with the certificates but not in the other applications? How do I correct the problem to make it work?

Thanks.


EDIT

Following @EnricoBasseti suggestions, I tried to enable/disabled (was disabled by default) the option "Follow Redirect" in CA API Gateway. It didn't change the error message.

I then tried to force Java to send SNI by modifiying my code :

SSLContext sslContext = SSLContext.getDefault();
SSLParameters sslParameters = new SSLParameters();
List<SNIServerName> sniHostNames = new ArrayList<>(1);
sniHostNames.add(new SNIHostName(url.getHost()));
sslParameters.setServerNames(sniHostNames);
SSLSocketFactory wrappedSSLSocketFactory = new SSLSocketFactoryWrapper(sslContext.getSocketFactory(), sslParameters);
HttpsURLConnection.setDefaultSSLSocketFactory(wrappedSSLSocketFactory);

HttpsURLConnection con = (HttpsURLConnection) url.openConnection();

But even with the SNI, I still get a Handshake failure if I use the full URL instead of only the domain name.


EDIT 2

With -Djavax.net.debug.all, I got more informations about the handshake failure :

verify_data:  { 216, 178, 49, 39, 2, 6, 100, 218, 54, 81, 137, 239 }
***
[write] MD5 and SHA1 hashes:  len = 16
0000: 14 00 00 0C D8 B2 31 27   02 06 64 DA 36 51 89 EF  ......1'..d.6Q..
Padded plaintext before ENCRYPTION:  len = 16
0000: 14 00 00 0C D8 B2 31 27   02 06 64 DA 36 51 89 EF  ......1'..d.6Q..
main, WRITE: TLSv1.2 Handshake, length = 40
[Raw write]: length = 45
0000: 16 03 03 00 28 00 00 00   00 00 00 00 00 44 C8 8F  ....(........D..
0010: A5 85 F7 18 F4 14 E3 9F   79 C6 4C 7B E3 50 61 59  ........y.L..PaY
0020: B0 83 38 DA 16 91 49 41   76 B3 E0 CD 17           ..8...IAv....
[Raw read]: length = 5
0000: 15 03 03 00 1A                                     .....
[Raw read]: length = 26
0000: 00 00 00 00 00 00 00 07   15 66 D8 8D F8 A5 88 D2  .........f......
0010: 44 24 BE 7B E4 1D 75 F7   58 C2                    D$....u.X.
main, READ: TLSv1.2 Alert, length = 26
Padded plaintext after DECRYPTION:  len = 2
0000: 02 28                                              .(
main, RECV TLSv1.2 ALERT:  fatal, handshake_failure
%% Invalidated:  [Session-1, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
%% Invalidated:  [Session-2, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
main, called closeSocket()
main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
main, called close()
main, called closeInternal(true)
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

Not sure if this means I'm lacking a specific cipher or not.


EDIT 3

With the "root" URL, I get this log :

verify_data:  { 40, 57, 242, 4, 89, 211, 6, 190, 109, 98, 13, 50 }
***
[write] MD5 and SHA1 hashes:  len = 16
0000: 14 00 00 0C 28 39 F2 04   59 D3 06 BE 6D 62 0D 32  ....(9..Y...mb.2
Padded plaintext before ENCRYPTION:  len = 16
0000: 14 00 00 0C 28 39 F2 04   59 D3 06 BE 6D 62 0D 32  ....(9..Y...mb.2
main, WRITE: TLSv1.2 Handshake, length = 40
[Raw write]: length = 45
0000: 16 03 03 00 28 00 00 00   00 00 00 00 00 5B 4A 01  ....(........[J.
0010: 6B 3B C3 43 29 8F EF CA   4B 85 85 93 BD 6C E3 9A  k;.C)...K....l..
0020: 2C D0 73 32 2A 33 1A 4A   5B 09 D1 A7 9C           ,.s2*3.J[....
[Raw read]: length = 5
0000: 14 03 03 00 01                                     .....
[Raw read]: length = 1
0000: 01                                                 .
main, READ: TLSv1.2 Change Cipher Spec, length = 1
[Raw read]: length = 5
0000: 16 03 03 00 28                                     ....(
[Raw read]: length = 40
0000: 00 00 00 00 00 00 00 00   72 1F F6 DD 23 77 96 2D  ........r...#w.-
0010: DB BC 1E 10 CC 8A 64 6E   8B C1 A9 04 8A 08 62 20  ......dn......b 
0020: 2A 13 E3 DD 56 57 C3 AB                            *...VW..
main, READ: TLSv1.2 Handshake, length = 40
Padded plaintext after DECRYPTION:  len = 16
0000: 14 00 00 0C 6E 14 F6 CB   16 CA D8 B2 68 96 70 19  ....n.......h.p.
*** Finished
verify_data:  { 110, 20, 246, 203, 22, 202, 216, 178, 104, 150, 112, 25 }
***
%% Didn't cache non-resumable client session: [Session-1, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
[read] MD5 and SHA1 hashes:  len = 16
[...] OMMITED (actual content of the URL) [...]

EDIT 4

Originally I couldn't import my PFX directly in my cacerts because I got the error Illegal Key Size. But since I configured "UnlimitedJCEPolicyJDK8", I was able to import it.

Then, following @dave_thompson_085 suggestions, I specified the keyStore to use in my Java code :

System.setProperty("javax.net.ssl.trustStore", "XXXXX/jdk1.8.0_71/jre/lib/security/cacerts");
System.setProperty("javax.net.ssl.trustStorePassword", "XXXXX");
System.setProperty("javax.net.ssl.keyStore", "XXXXX/jdk1.8.0_71/jre/lib/security/cacerts");
System.setProperty("javax.net.ssl.keyStorePassword", "XXXXX");

I first got the error java.security.UnrecoverableKeyException: Cannot recover key because I stored the key with a password different from the keystore's. But when I used the same password for the two elements, it worked correctly.

If I understand correctly, I was only sending the certificate but not my private key. I know have to find a way to do the same in CA API Gateway and Software AG Integration Server.

0
задан 2 May 2018 в 12:16
2 ответа

Благодаря @ dave_thompson_085 и @EnricoBasseti мне удалось заставить его работать с моими 3 приложениями.

Вот что я сделал для каждого one:

1) CA Technologies - Управление API

В мастере импорта «Управление закрытым ключом» я добавил ключ из файла PFX. Затем в мастере добавления «Управление сертификатом» я импортировал сертификат из цепочки сертификатов закрытого ключа (4-й вариант). Затем я использовал мастер добавления «Управление сертификатом», чтобы импортировать сертификат из URL-адреса API (в моем случае другой сертификат).

Затем в моей политике я щелкнул правой кнопкой мыши свое утверждение «Маршрут к HTTP (S)» и нажал на «Выбрать закрытый ключ», чтобы заставить CA API Gateway использовать закрытый ключ, который я импортировал ранее, вместо ключа по умолчанию.

2) Software AG - Сервер интеграции

В веб-консоли myWebmethod в разделах «Безопасность» и «Хранилище ключей» ", Я щелкнул" Создать псевдоним хранилища ключей ". Я настроил новое хранилище ключей PCKS12 и дал ему свой файл PFX (нужно было указать пароль хранилища ключей и пароль ключа отдельно).

Затем в «Безопасность» и «Сертификаты» я щелкнул «Настроить сертификаты клиента» и добавил корневой сертификат из моей цепочки сертификации (ранее я экспортировал ее с помощью IE). Я также добавил корневой сертификат из цепочки сертификации URL (в моем случае другой).

Наконец, в моей службе потока перед использованием компонента pub.client: http я добавил файл pub.security.keystore: setKeyAndChain "компонент, настроенный для использования моего нового псевдонима KeyStore и его псевдонима Key.

3) Java 8 через HttpsURLConnection

Мне пришлось заменить мои файлы политики безопасности JDK на файлы, предоставленные oracle: UnlimitedJCEPolicyJDK8 (только при использовании JDK8 <161). Затем я импортировал пару ключей в свой файл cacerts (% JAVA_HOME% / jre / lib / security / cacerts). Я также импортировал корневой сертификат URL-адреса API (который отличается от того, который содержится в моем файле PFX).

Затем в моем коде я заставил Java использовать мои cacerts в качестве хранилища ключей (см. ИЗМЕНИТЬ 4 из моего исходного сообщения ).

0
ответ дан 24 November 2019 в 02:44

TLS-соединение устанавливается до HTTP-запроса, поэтому нет никакого способа определить (на стороне сервера), какую конечную точку вы запрашиваете (только имя хоста с использованием SNI). Вот почему сертификаты не включают полный URL.

Итак, сбой при рукопожатии для другого URL-адреса очень странный - может быть, URL-адрес конечной точки выполняет 302 перенаправление на другой сервер? Посмотрите, есть ли в используемом вами приложении параметры для отключения параметра Следовать перенаправлениям HTTP (или что-то в этом роде).

Вы также можете использовать Internet Explorer, чтобы проверить это: посмотрите, изменится ли URL-адрес, когда вы пытаетесь достичь https://api.xxx.com/method/

0
ответ дан 24 November 2019 в 02:44

Теги

Похожие вопросы