In Python I was using requests like this:
requests.put( webdavURL, auth=(tUsername, tPassword), data=webdavFpb, verify=False, cert=("/path/to/file.pem", "/path/to/file.key"))
Easy as pie.
Now I need to implement the same thing in Java using Apache HttpClient. How can I pass a client certificate when making requests using HttpClient?
You need to send the client certificate during the TLS handshake before anything HTTP (methods, headers, URLs, request bodies) is available to be influenced. The server will not accept a client certificate sent later.
I think the main difference is that in java, you usually put the key and the certificate to a key store and use it from there. Like you mention often people do want to use a separate library for it, like mentioned httpcomponents client (just like you're using requests library in your python example).
Here's an example of using a client certificate from a key store, using the previously mentioned library:
import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; import org.junit.Test; import javax.net.ssl.SSLContext; import java.io.InputStream; import java.security.KeyStore; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; public class MyClientCertTest { private static final String KEYSTOREPATH = "/clientkeystore.jks"; // or .p12 private static final String KEYSTOREPASS = "keystorepass"; private static final String KEYPASS = "keypass"; KeyStore readStore() throws Exception { try (InputStream keyStoreStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) { KeyStore keyStore = KeyStore.getInstance("JKS"); // or "PKCS12" keyStore.load(keyStoreStream, KEYSTOREPASS.toCharArray()); return keyStore; } } @Test public void readKeyStore() throws Exception { assertNotNull(readStore()); } @Test public void performClientRequest() throws Exception { SSLContext sslContext = SSLContexts.custom() .loadKeyMaterial(readStore(), KEYPASS.toCharArray()) // use null as second param if you don't have a separate key password .build(); HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build(); HttpResponse response = httpClient.execute(new HttpGet("https://slsh.iki.fi/client-certificate/protected/")); assertEquals(200, response.getStatusLine().getStatusCode()); HttpEntity entity = response.getEntity(); System.out.println("----------------------------------------"); System.out.println(response.getStatusLine()); EntityUtils.consume(entity); } }
Maven pom for dependency versions:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.acme</groupId> <artifactId>httptests</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.9</version> <!-- this is not needed, but useful if you want to debug what's going on with your connection --> <configuration> <argLine>-Djavax.net.debug=all</argLine> </configuration> </plugin> </plugins> </build> </project>
I've also published a simple test page for testing a client certificate.
Just to demonstrate that it can be done, below is an example of using client certificate just using standard java api, without extra libraries.
import org.junit.Test; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.security.KeyStore; public class PlainJavaHTTPS2Test { @Test public void testJKSKeyStore() throws Exception { final String KEYSTOREPATH = "clientkeystore.jks"; final char[] KEYSTOREPASS = "keystorepass".toCharArray(); final char[] KEYPASS = "keypass".toCharArray(); try (InputStream storeStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) { setSSLFactories(storeStream, "JKS", KEYSTOREPASS, KEYPASS); } testPlainJavaHTTPS(); } @Test public void testP12KeyStore() throws Exception { final String KEYSTOREPATH = "clientkeystore.p12"; final char[] KEYSTOREPASS = "keystorepass".toCharArray(); final char[] KEYPASS = "keypass".toCharArray(); try (InputStream storeStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) { setSSLFactories(storeStream, "PKCS12", KEYSTOREPASS, KEYPASS); } testPlainJavaHTTPS(); } private static void setSSLFactories(InputStream keyStream, String keystoreType, char[] keyStorePassword, char[] keyPassword) throws Exception { KeyStore keyStore = KeyStore.getInstance(keystoreType); keyStore.load(keyStream, keyStorePassword); KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyFactory.init(keyStore, keyPassword); KeyManager[] keyManagers = keyFactory.getKeyManagers(); SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(keyManagers, null, null); SSLContext.setDefault(sslContext); } public void testPlainJavaHTTPS() throws Exception { String httpsURL = "https://slsh.iki.fi/client-certificate/protected/"; URL myUrl = new URL(httpsURL); HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection(); try (InputStream is = conn.getInputStream()) { InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String inputLine; while ((inputLine = br.readLine()) != null) { System.out.println(inputLine); } } } }
And here's a third version with least amount of code, but which relies on the fact that a) keystore is a file on disk, not within jar, and b) key password must be identical to keystore password.
import org.junit.BeforeClass; import org.junit.Test; import java.net.URL; import java.io.*; import javax.net.ssl.HttpsURLConnection; public class PlainJavaHTTPSTest { @BeforeClass public static void setUp() { System.setProperty("javax.net.ssl.keyStore", "/full/path/to/clientkeystore-samepassword.jks"); System.setProperty("javax.net.ssl.keyStorePassword", "keystorepass"); } @Test public void testPlainJavaHTTPS() throws Exception { String httpsURL = "https://slsh.iki.fi/client-certificate/protected/"; URL myUrl = new URL(httpsURL); HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection(); try (InputStream is = conn.getInputStream()) { InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String inputLine; while ((inputLine = br.readLine()) != null) { System.out.println(inputLine); } } } }
The properties set above in code can of course be also given as startup parameters, -Djavax.net.ssl.keyStore=/full/path/to/clientkeystore-samepassword.jks
and -Djavax.net.ssl.keyStorePassword=keystorepass
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With