Reputation: 3861
Someone on github asked me a question regarding my library. This library provides some factory classes to easily create a sslcontext. I make sure not the share the details of the library and just share plain java code and the additional library which I used. I tried to debug it and also tried reformatting of the certificates but it does not work. The exception which I got is:
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
The java trustmanager is basically saying I received the certificate of the server and I just checked in your list of trusted certificates and I couldn't find a matching certificate. So I don't trust this server and therefor I am stopping this request. When I debugged it I could understand why the trustmanager throw an exception because there was no matching certificate at all which was the same as the server. However when I use the same files with curl it works, so I was not quite sure if there is an issue with the certificates and the key material, or maybe java ssl implementation has some limitations or maybe I am not configuring it correctly... I hope someone can explain me the difference in behaviour of why it fails with java and passes with curl.
The private key content of the client:
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCw9Of6paN76eY7MApVZrPLt3Ss9rYOSkUHqw9ls8ewxLqlfk/S
7tdQ6g1+fa4HnKeSXRRrBv7RNlnwRPzBIha5QSwVSraT079xnEP/MGlZ+lyS4Kad
xv/m96jJXk9w8GaHY8OiwfJqcTolVKVEzDKVDIxp3noYfCRVW4RDm0FMJwIDAQAB
AoGARQMOYbMtogrjblva+9l071MZ3sbM05/lcgslkx1dGLRwslAjo3jgYj8ViipL
r85JkAxbBS6SPFd9FfZhuJSp1WnXAP63GHB8NUb+hqrFqDxLuNsFQ1Q04+0FDlJw
+YUD7QZWwXy28clt9arEIoPeikOmePDQF7FGuU6f0+aNxAECQQDcWFfTDLvAMPNX
T6WsuYYIs+1mYYxImElsaTfm36Ro9C+2atVnxnqY6JFhWh6Sn+7dNiWS4vsbH1uZ
7EiQKpoBAkEAzZc5jxmfG/5f8uKQh49ORTaIOSyivd/L60rVNcdRyukFrFPTb/Ey
o2LsvDzDclfdGdhehkEAdX+Biksz0gfWJwJAIvuvreVWpbPf3pvZnOuzmQwgA+I2
6IutFJY79t7I9pTWQmsByMEdU8uQ0VkCg5r6zIo9Ou3omizHWU/HUYRCAQJAJXmN
SmJXOFkT0EgwJCWhFMit6A4U1Bt5JjiLyLO+WwhCunjFL8B9hH7BvEYvMiaF7PId
uMcceE53pGe02HIJPQJAXuCB+O0zqnSci3dP5cH724qNbRMvH0IvI6PFK8RIEd7I
d+HKKVGXhPC1mMhIbAqydxmTCDewsSH9rlghhTvqIg==
-----END RSA PRIVATE KEY-----
The certificate chain content of the client:
-----BEGIN CERTIFICATE-----
MIICXDCCAcUCAQEwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCSUUxEDAOBgNV
BAgMB0RvbmVnYWwxFDASBgNVBAcMC0xldHRlcmtlbm55MRQwEgYDVQQDDAt0cGVh
cnNvbi5pZTEjMCEGCSqGSIb3DQEJARYUcm9vdGNlcnRAdHBlYXJzb24uaWUwHhcN
MjEwMTA4MDE1MDUzWhcNMjIwMTA4MDE1MDUzWjB9MQswCQYDVQQGEwJJRTEQMA4G
A1UECAwHRG9uZWdhbDEUMBIGA1UEBwwLTGV0dGVya2VubnkxHzAdBgNVBAMMFmNs
aWVudGNlcnQudHBlYXJzb24uaWUxJTAjBgkqhkiG9w0BCQEWFmNsaWVudGNlcnRA
dHBlYXJzb24uaWUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALD05/qlo3vp
5jswClVms8u3dKz2tg5KRQerD2Wzx7DEuqV+T9Lu11DqDX59rgecp5JdFGsG/tE2
WfBE/MEiFrlBLBVKtpPTv3GcQ/8waVn6XJLgpp3G/+b3qMleT3DwZodjw6LB8mpx
OiVUpUTMMpUMjGneehh8JFVbhEObQUwnAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEA
bwGc1UM0qhKrJdjhXiXcVcf+ebyQKJHe3wXX3ffdw7Y7Gg9CjbD21kdlTVlSJrLT
hC5k01NChPDScK0C5CUPjEtHCt/G4sEPy+R1tXAmOBMnJXHqHQ/gsk1xe2TXZTep
uX5Al1vhtY7iVOnxsoRt2rdvYLRhpvf+7qbrchlQtXE=
-----END CERTIFICATE-----
The trusted certificate, ca
-----BEGIN CERTIFICATE-----
MIICVzCCAcACCQCr1Uz2zH5PwzANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJJ
RTEQMA4GA1UECAwHRG9uZWdhbDEUMBIGA1UEBwwLTGV0dGVya2VubnkxFDASBgNV
BAMMC3RwZWFyc29uLmllMSMwIQYJKoZIhvcNAQkBFhRyb290Y2VydEB0cGVhcnNv
bi5pZTAeFw0yMTAxMDgwMTQ0NDRaFw0yMjAxMDgwMTQ0NDRaMHAxCzAJBgNVBAYT
AklFMRAwDgYDVQQIDAdEb25lZ2FsMRQwEgYDVQQHDAtMZXR0ZXJrZW5ueTEUMBIG
A1UEAwwLdHBlYXJzb24uaWUxIzAhBgkqhkiG9w0BCQEWFHJvb3RjZXJ0QHRwZWFy
c29uLmllMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJ0E9LXa/6XNzm1CZ3
lHLez768tSAmrQ0qYFCiyrGhnfe+xJJAf7BSWRAUQmM7ULRj89NVMor7GyBYH1mq
1r9dI23i3KM8ZAeBN+32eifOH/TqE2QKLm2ORqBzNXsl1QTriXLR4aIs8bYUH6JP
9qGE24ncqeO1/Ry6o2DxQWEkLQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADq/CDEC
TwmegkvOMTH7mlWWgaoVWgBU6mtUjm2fJtQOFDewicQlxa2jF3LwDwb94J5vu6Kc
NAZwLWE70+pWhhcsXWc0rbA1Ayv1xkyi8LPWqqmCbz14R2tEDuPZJxKvS0DkGKqy
De4sdJsOo2GWhYsmY6HiOPElt5ndzI5NRZ6s
-----END CERTIFICATE-----
So the following curl command will successfully execute and gives me the correct response body:
curl --cacert ca.pem --key client-key.pem --cert client-cert.pem https://prod.idrix.eu/secure/
To load these files in Java I use Bouncy Castle, below is the full snippet of parsing it, loading it into TrustManager, KeyManager, creating the SSLContext and executing the request:
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Objects;
import java.util.stream.Collectors;
public class App {
private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
private static final JcaPEMKeyConverter KEY_CONVERTER = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER);
private static final JcaX509CertificateConverter CERTIFICATE_CONVERTER = new JcaX509CertificateConverter().setProvider(BOUNCY_CASTLE_PROVIDER);
public static void main(String[] args) throws Exception {
String clientKeyContent = App.getContent(App.class.getClassLoader().getResourceAsStream("client-key.pem"));
String clientCertificateChainContent = App.getContent(App.class.getClassLoader().getResourceAsStream("client-cert.pem"));
String caCertificateContent = App.getContent(App.class.getClassLoader().getResourceAsStream("ca.pem"));
Reader reader = new StringReader(clientKeyContent);
PEMParser pemParser = new PEMParser(reader);
Object object = pemParser.readObject();
PrivateKeyInfo privateKeyInfo = ((PEMKeyPair) object).getPrivateKeyInfo();
PrivateKey clientKey = KEY_CONVERTER.getPrivateKey(privateKeyInfo);
reader.close();
pemParser.close();
reader = new StringReader(clientCertificateChainContent);
pemParser = new PEMParser(reader);
object = pemParser.readObject();
X509Certificate clientCertificateChain = CERTIFICATE_CONVERTER.getCertificate((X509CertificateHolder) object);
reader.close();
pemParser.close();
reader = new StringReader(caCertificateContent);
pemParser = new PEMParser(reader);
object = pemParser.readObject();
X509Certificate caCertificate = CERTIFICATE_CONVERTER.getCertificate((X509CertificateHolder) object);
reader.close();
pemParser.close();
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, "password".toCharArray());
keyStore.setKeyEntry("client", clientKey, "".toCharArray(), new Certificate[]{clientCertificateChain});
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "".toCharArray());
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, "password".toCharArray());
trustStore.setCertificateEntry("ca", caCertificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
HttpClient httpClient = HttpClient.newBuilder()
.sslContext(sslContext)
.build();
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create("https://prod.idrix.eu/secure/"))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
public static String getContent(InputStream inputStream) throws IOException {
try (InputStreamReader inputStreamReader = new InputStreamReader(Objects.requireNonNull(inputStream), StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
return bufferedReader.lines()
.collect(Collectors.joining(System.lineSeparator()));
}
}
}
I am using Java 11 and Bouncy Castle, see below for the specific artifact-id:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.68</version>
</dependency>
So for me it is not clear why the curl command works and the setup in java fails. Did I configured something wrong, any help would be great! I tried to provide as much as details possible. The private key is a test key so it won't harm to share it here.
Upvotes: 3
Views: 2200
Reputation: 3861
I discovered what the issue was and so I wanted to share with others who have this in common.
It looks like on mac os x curl is by default using the system trusted certificates and on java side this is not the case. I am not sure what the behaviour of curl is with other operating systems. https://prod.idrix.eu/secure/
has a certificate which is signed by DST Root CA X3
I disabled this and rerun the exact same setup as described on my initial question and the curl https request fails, just like it also failed on java side.
I disabled it from keychain as shown here:
Upvotes: 2