We have a java application that reads our customers public calendars - for example from google (https://calendar.google.com/calendar/ical/...) or icloud (webcal://p58-caldav.icloud.com/published/...).
Until recently this was working, but it is now failing for icloud calendars with the following error:
javax.net.ssl.SSLHandshakeException: Received fatal alert: decode_error
Google calendars still work.
Our application reads icloud calendars by swapping webcal:// for https:// and doing an http get on the url.
Eg. https://p58-caldav.icloud.com/published/2/MTg3MDA3MjYwMTE4NzAwN1KPAcrTfGuhFJXbGYJ9wEYJFNzP10cp8mw6gSLjUVU_
Given that hitting this url in a browser successfully returns a valid icalendar (ics) result our first thought was that when running in a jvm this endpoint's root ca certificate were not in the keystore for the jvm.
We used the following command to get the certificates for this endpoint:
openssl s_client -servername p58-caldav.icloud.com -host p58-caldav.icloud.com -port 443 -prexit -showcerts
And then added both of the certificates in the chain to our keystore:
keytool -importcert -file apple-ist-ca-2-g1.crt -alias apple-ist-ca-2-g1 -trustcacerts -keystore <java home>/lib/security/cacerts -storepass <pass>
keytool -importcert -file caldav.icloud.com.crt -alias caldav.icloud.com -trustcacerts -keystore <java home>/lib/security/cacerts -storepass <pass>
We also added code to our test case to verify that these certificates were being picked up by the jvm.
This did not work and we get the same error.
We are running openjdk version "11.0.2" so I do not believe that there are any issues with SNI or missing ciphers (though I could be wrong!)
The following very simple code example reproduces the error:
public class CalReader {
public static void main(String[] args) {
hitAPI("webcal://p58-caldav.icloud.com/published/2/MTg3MDA3MjYwMTE4NzAwN1KPAcrTfGuhFJXbGYJ9wEYJFNzP10cp8mw6gSLjUVU_");
}
private static void hitAPI(final String url) {
String cleaned = url.replace("webcal://", "https://").trim();
InputStream in = null;
try {
URL toUse = new URL(cleaned);
URLConnection conn = toUse.openConnection();
in = conn.getInputStream();
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try {
if (in != null) {
in.close();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
Adding ssl debugging (-Djavax.net.debug=ssl:handshake:verbose) does not (to my eye) provide any pertinent information. However, I admit that I am not an ssl expert.
javax.net.ssl|DEBUG|01|main|2019-07-18 11:51:53.042 AEST|ClientHello.java:651|Produced ClientHello handshake message (
"ClientHello": {
"client version" : "TLSv1.2",
"random" : "BC DF 54 80 F2 0B 9A F9 42 17 4A FF B9 B5 65 22 C4 9E 19 63 0A 88 81 1F 84 2A 74 2D 40 40 F2 F0",
"session id" : "A9 21 0D FE 9B 8E 9F B8 53 17 60 09 D4 B2 C8 EE BE FD 7F AC 93 57 12 4B 47 BA FF A9 7A DF C4 EF",
"cipher suites" : "[TLS_AES_128_GCM_SHA256(0x1301), TLS_AES_256_GCM_SHA384(0x1302), TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B), TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xC030), TLS_RSA_WITH_AES_256_GCM_SHA384(0x009D), TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384(0xC02E), TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384(0xC032), TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(0x009F), TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(0x00A3), TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC02F), TLS_RSA_WITH_AES_128_GCM_SHA256(0x009C), TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256(0xC02D), TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256(0xC031), TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x009E), TLS_DHE_DSS_WITH_AES_128_GCM_SHA256(0x00A2), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(0xC024), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(0xC028), TLS_RSA_WITH_AES_256_CBC_SHA256(0x003D), TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384(0xC026), TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384(0xC02A), TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(0x006B), TLS_DHE_DSS_WITH_AES_256_CBC_SHA256(0x006A), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xC00A), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC014), TLS_RSA_WITH_AES_256_CBC_SHA(0x0035), TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA(0xC005), TLS_ECDH_RSA_WITH_AES_256_CBC_SHA(0xC00F), TLS_DHE_RSA_WITH_AES_256_CBC_SHA(0x0039), TLS_DHE_DSS_WITH_AES_256_CBC_SHA(0x0038), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(0xC023), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(0xC027), TLS_RSA_WITH_AES_128_CBC_SHA256(0x003C), TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256(0xC025), TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256(0xC029), TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(0x0067), TLS_DHE_DSS_WITH_AES_128_CBC_SHA256(0x0040), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xC009), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC013), TLS_RSA_WITH_AES_128_CBC_SHA(0x002F), TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA(0xC004), TLS_ECDH_RSA_WITH_AES_128_CBC_SHA(0xC00E), TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x0033), TLS_DHE_DSS_WITH_AES_128_CBC_SHA(0x0032)]",
"compression methods" : "00",
"extensions" : [
]
}
)
javax.net.ssl|DEBUG|01|main|2019-07-18 11:51:53.046 AEST|Alert.java:232|Received alert message (
"Alert": {
"level" : "fatal",
"description": "decode_error"
}
)
We have just tried this in a java 8 environment and it works as expected. As a quick test, we copied our java 8 environment cacerts to our java 11 environment, however, it still does not work. What else is there in the java 8 environment that would allow it to work?
It is difficult to determine, but it appears that this has happened in the last 2-4 weeks or so. We have evidence of a successful read of an icloud calendar on June 24th, and we are seeing the above error from July 4th.
We have tried multiple icloud calendars and get the same result for each, so it does not appear to be related to the calendar itself.
UPDATE
We forced the use of TLS1.2 and it works. For the example above:
java -Dhttps.protocols=TLSv1.2 CalReader
This begs the question, do Apple endpoints have an issue with TLS1.3? Or is it something else?
We did find an article which indicated an issue with TLS1.3 in java 11 (https://webtide.com/openjdk-11-and-tls-1-3-issues/), but we had already tried with a java 12 version with no success, so I don't think that was the issue.
So, we have to decide now whether to regress with a forced use of TLS1.2 or keep searching for another solution.
See Question&Answers more detail:
os